-
Notifications
You must be signed in to change notification settings - Fork 6
[NAE-2182] Clearing actions cache #348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/7.0.0-rev9
Are you sure you want to change the base?
Conversation
…ache endpoints for actionsCache, functionsCache and namespaceFunctionsCache
WalkthroughAdds bounded generic caches and a global functions cache, introduces default/latest PetriNet version semantics with import/version lifecycle handling, updates callers/tests from "newest" to "default/latest", adds function validation, and exposes actuator endpoints. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Caller
participant FieldActionsCacheService as FACS
participant CacheManager
participant ActionsCache as "Cache(actions)"
participant PetriNetService as PNS
Caller->>FACS: request compiled action (fieldId, actionRef)
FACS->>CacheManager: getCache("actions")
CacheManager-->>FACS: ActionsCache
FACS->>ActionsCache: get(key, loader)
alt cache hit
ActionsCache-->>FACS: compiledClosure
else cache miss
FACS->>PNS: getDefaultVersionByIdentifier(petriNetId)
PNS-->>FACS: PetriNet
FACS->>FACS: compile/rehydrate closure
FACS->>ActionsCache: put(key, compiledClosure)
end
FACS-->>Caller: return Closure
sequenceDiagram
autonumber
participant Caller
participant FACS
participant CacheManager
participant GlobalFuncs as "Cache(globalFunctions)"
participant PNS
Caller->>FACS: reloadCachedFunctions(petriNetId)
FACS->>CacheManager: getCache("globalFunctions")
CacheManager-->>FACS: GlobalFuncs
FACS->>GlobalFuncs: evict(petriNetId)
FACS->>PNS: getDefaultVersionByIdentifier(petriNetId)
PNS-->>FACS: PetriNet (or null)
alt PetriNet found
FACS->>FACS: evaluate/compile functions
FACS->>GlobalFuncs: put(petriNetId, evaluatedFunctions)
else not found
FACS-->>Caller: handled (no-op)
end
FACS-->>Caller: void
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (6)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
3-7: Make utility holder non-instantiable and final.Prevents accidental instantiation/extension and clarifies intent.
-public class CacheMapKeys { +public final class CacheMapKeys { public static final String ACTIONS = "actionsCache"; public static final String FUNCTIONS = "functionsCache"; public static final String NAMESPACE_FUNCTIONS = "namespaceFunctionsCache"; + + private CacheMapKeys() { + throw new AssertionError("No instances"); + } }application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
22-24: Leaking the mutable Map via getNativeCache couples callers to internals.FieldActionsCacheService manipulating the map directly bypasses cache invariants/locking.
Prefer cache APIs (
get/put/evict/clear). If direct access is required, document it and ensure all such usage is confined and synchronized.application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
43-65: Confirm thread-safety of MaxSizeHashMap or wrap it.If
MaxSizeHashMapis not concurrent, concurrent access through the cache will race.Options:
- Wrap:
() -> java.util.Collections.synchronizedMap(new MaxSizeHashMap<>(...))- Or rely on the synchronized GenericMapCache changes above.
- Add a brief Javadoc on
MaxSizeHashMapusage here clarifying concurrency expectations.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (3)
142-143: Avoid raw cast in getNamespaceFunctionCache; ensure type-safety and null-safety.Keeps method contract explicit and prevents CCE if the impl changes.
- return new HashMap<>((Map) cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).getNativeCache()); + Map<String, List<CachedFunction>> nativeMap = + (Map<String, List<CachedFunction>>) getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS).getNativeCache(); + return new HashMap<>(nativeMap);
147-148: Clear methods: use guarded cache retrieval.- cacheManager.getCache(CacheMapKeys.ACTIONS).clear(); + getRequiredCache(CacheMapKeys.ACTIONS).clear(); @@ - cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).clear(); + getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS).clear(); @@ - cacheManager.getCache(CacheMapKeys.FUNCTIONS).clear(); + getRequiredCache(CacheMapKeys.FUNCTIONS).clear();Also applies to: 151-153, 156-158
74-84: Keep this service cache-implementation-agnostic; avoidgetNativeCache()andinstanceof Map.Relying on the native Map couples you to
GenericMapCacheand its thread-safety characteristics. UsingCacheAPIs (get(key, Callable),evict,clear) preserves portability and simplifies reasoning.Also applies to: 89-99, 142-143
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java(4 hunks)application-engine/src/main/resources/application.yaml(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(8-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: Build
- GitHub Check: task-list-completed
🔇 Additional comments (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (2)
67-70: CacheManager wiring LGTM.Unified
SimpleCacheManagerwith default and specialized caches is clean and extensible.
37-41: Exclude specialized cache names from default caches. Filter outCacheMapKeys.ACTIONS,CacheMapKeys.FUNCTIONSandCacheMapKeys.NAMESPACE_FUNCTIONSfrom theproperties.getAllCaches()stream before mapping toConcurrentMapCacheto avoid registering those caches twice.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (1)
28-37: Verify FieldActionsCacheService bean registration
Confirm that FieldActionsCacheService is annotated (e.g. @service or @component) or declared via a @bean method so Spring will pick up the new constructor signature, and search your configuration for any @bean methods or manual instantiations of FieldActionsCacheService using the old two-arg constructor.
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Show resolved
Hide resolved
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Outdated
Show resolved
Hide resolved
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Outdated
Show resolved
Hide resolved
.../src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Outdated
Show resolved
Hide resolved
.../src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Outdated
Show resolved
Hide resolved
| Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS); | ||
| Object nativeFunctionsCache = functionsCache.getNativeCache(); | ||
|
|
||
| if (nativeFunctionsCache instanceof Map<?, ?> map) { | ||
| functions.forEach(function -> { | ||
| if (!map.containsKey(function.getStringId())) { | ||
| functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); | ||
| } | ||
| cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid native Map access in getCachedFunctions; compute-if-absent via Cache API.
Works with any Cache impl and removes silent empty-return if native cache isn’t a Map.
- Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS);
- Object nativeFunctionsCache = functionsCache.getNativeCache();
-
- if (nativeFunctionsCache instanceof Map<?, ?> map) {
- functions.forEach(function -> {
- if (!map.containsKey(function.getStringId())) {
- functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
- }
- cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
- });
- }
+ Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
+ for (Function function : functions) {
+ CachedFunction cf = functionsCache.get(function.getStringId(),
+ () -> CachedFunction.build(shell, function));
+ cachedFunctions.add(cf);
+ }Committable suggestion skipped: line range outside the PR's diff.
| exclude: shutdown | ||
| include: "*" | ||
| endpoint: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Actuator exposure is fully open; combined with anonymous '/manage/' this is a security risk.**
include: "*" exposes sensitive endpoints (env, beans, heapdump, caches). Your config also lists /manage/** under anonymous exceptions (Lines 87–88), making these endpoints publicly accessible.
Apply both changes:
- anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/manage/**
+ anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover exposure:
exclude: shutdown
- include: "*"
+ include: health,info,metrics,prometheus,cachesOptional hardening:
- Require auth/role for /manage/** or move management to a separate port.
- Only include the endpoints you actually need in non-dev profiles.
🤖 Prompt for AI Agents
In application-engine/src/main/resources/application.yaml around lines 119–121
(and note anonymous /manage/** at lines 87–88), the actuator exposure is set to
include: "*" which together with anonymous access to /manage/** exposes
sensitive endpoints; change the actuator exposure to list only the required
endpoints (e.g., health, info, metrics) instead of "*" and remove or restrict
the anonymous exception for /manage/**—either require authentication/appropriate
role for /manage/** or move management endpoints to a separate management port;
make these config edits in the relevant profile(s) and/or add security
constraints so management endpoints are not publicly accessible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
♻️ Duplicate comments (5)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
15-15: Make map-backed cache thread-safe; compute-and-store must be atomic.Backed by a plain Map with unsynchronized mutations; races with put/evict/clear and get(loader). Clear also reassigns the map mid-flight. Synchronize mutations and use double-checked locking in get(loader).
public abstract class GenericMapCache<V> implements Cache { @@ - protected volatile Map<String, V> map; + protected volatile Map<String, V> map; + private final Object monitor = new Object(); @@ - @Override - public <T> T get(Object key, java.util.concurrent.Callable<T> loader) { + @Override + public <T> T get(Object key, java.util.concurrent.Callable<T> loader) { String stringKey = String.valueOf(key); - Object present = map.get(stringKey); + Object present = map.get(stringKey); if (present != null) { return (T) present; } - try { - T computed = loader.call(); - if (computed != null) { - map.put(stringKey, safeCast(computed)); - } - return computed; - } catch (Exception ex) { - throw new org.springframework.cache.Cache.ValueRetrievalException(stringKey, loader, ex); - } + synchronized (monitor) { + present = map.get(stringKey); + if (present != null) { + return (T) present; + } + try { + T computed = loader.call(); + if (computed != null) { + map.put(stringKey, safeCast(computed)); + } + return computed; + } catch (Exception ex) { + throw new org.springframework.cache.Cache.ValueRetrievalException(stringKey, loader, ex); + } + } } @@ - public void put(Object key, Object value) { - map.put(String.valueOf(key), safeCast(value)); + public void put(Object key, Object value) { + synchronized (monitor) { + map.put(String.valueOf(key), safeCast(value)); + } } @@ - public void evict(Object key) { - map.remove(String.valueOf(key)); + public void evict(Object key) { + synchronized (monitor) { + map.remove(String.valueOf(key)); + } } @@ - public void clear() { - this.map = mapFactory.get(); + public void clear() { + synchronized (monitor) { + this.map = mapFactory.get(); + } log.info("{} cache cleared", this.getName()); }Also applies to: 33-51, 53-67
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4)
55-62: Fail fast on missing cache; avoid NPE.- Cache namespaceFunctionsCache = cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS); + Cache namespaceFunctionsCache = getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS); @@ - namespaceFunctionsCache.evictIfPresent(petriNet.getIdentifier()); + namespaceFunctionsCache.evictIfPresent(petriNet.getIdentifier());Support code (add once in this class):
private Cache getRequiredCache(String name) { Cache cache = cacheManager.getCache(name); if (cache == null) throw new IllegalStateException("Cache '" + name + "' not configured"); return cache; }
65-69: Guard cache retrieval and reload atomically.- public void reloadCachedFunctions(String petriNetId) { - cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNetId); - cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId)); - } + public void reloadCachedFunctions(String petriNetId) { + getRequiredCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNetId); + cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId)); + }
79-89: Use Cache APIs; avoid peeking into native Map and eliminate TOCTOU.- Cache actionsCache = cacheManager.getCache(CacheMapKeys.ACTIONS); - Object nativeActionsCache = actionsCache.getNativeCache(); - - if (nativeActionsCache instanceof Map<?, ?> map) { - if (shouldRewriteCachedActions || !map.containsKey(stringId) ) { - Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); - actionsCache.put(stringId, code); - } - } - return (Closure) actionsCache.get(stringId).get(); + Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS); + if (shouldRewriteCachedActions) { + actionsCache.evict(stringId); + } + return actionsCache.get(stringId, + () -> (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"));
93-105: Compute functions atomically and avoid native cache access.- Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS); - Object nativeFunctionsCache = functionsCache.getNativeCache(); - - if (nativeFunctionsCache instanceof Map<?, ?> map) { - functions.forEach(function -> { - if (!map.containsKey(function.getStringId())) { - functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); - } - cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); - }); - } + Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); + for (Function function : functions) { + CachedFunction cf = functionsCache.get(function.getStringId(), + () -> CachedFunction.build(shell, function)); + cachedFunctions.add(cf); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java(3 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java(4 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
PR: netgrif/application-engine#334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
🧬 Code graph analysis (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(10-80)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
Slf4j(10-41)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(10-80)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
Slf4j(10-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Test
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (6)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)
4-4: No action on wildcard import.Acceptable here unless the project enforces “no wildcard imports”.
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (2)
8-8: No action on wildcard import.Safe refactor.
558-569: Add index on functions._id in petriNet collection
The query in PetriNetService.findByFunctionId filters on functions._id; without an index this will trigger a full collection scan. Verify (via @indexed on Function.objectId, a database migration, or manual index setup) that an index exists for functions._id.application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
15-17: Ignore namespace vs PetriNet ID key concernAll cache operations on
NAMESPACE_FUNCTIONSusepetriNet.getIdentifier()as the key (see FieldActionsCacheService.java:55 and 67), so the cache is intentionally keyed by PetriNet ID. No changes required.Likely an incorrect or invalid review comment.
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
13-13: Confirm reload key semantics
reloadCachedFunctions(stringKey)presumesstringKeyis the Petri net identifier the service expects. Please confirm the key format aligns withIFieldActionsCacheService’s contract; otherwise, misses will never populate.I can generate a short repo scan to locate all
reloadCachedFunctions(...)overloads and usages to confirm the expected key shape. Do you want that?application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
46-50: Filtering specialized cache names is unnecessary CacheConfigurationProperties.getAllCaches() returns only the default caches (petriNetById,petriNetByIdentifier,petriNetNewest,petriNetCache,loadedModules) and any user-added names—it never includesCacheMapKeys.ACTIONS,CacheMapKeys.FUNCTIONS, orCacheMapKeys.NAMESPACE_FUNCTIONS, so no duplicate registrations can occur by default.Likely an incorrect or invalid review comment.
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
Outdated
Show resolved
Hide resolved
| @Override | ||
| public ValueWrapper get(Object key) { | ||
| String stringKey = String.valueOf(key); | ||
|
|
||
| Object valueObject = map.get(stringKey); | ||
| if (valueObject != null) { | ||
| return new org.springframework.cache.support.SimpleValueWrapper(valueObject); | ||
| } | ||
| fieldActionsCacheService.reloadCachedFunctions(stringKey); | ||
| return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache.get(Object) should return null on miss; don’t wrap null
Returning SimpleValueWrapper(null) violates the Cache contract. Return null when the key is absent or the reload doesn’t populate the key.
@Override
public ValueWrapper get(Object key) {
String stringKey = String.valueOf(key);
Object valueObject = map.get(stringKey);
if (valueObject != null) {
return new org.springframework.cache.support.SimpleValueWrapper(valueObject);
}
fieldActionsCacheService.reloadCachedFunctions(stringKey);
- return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey));
+ Object refreshed = map.get(stringKey);
+ return refreshed != null
+ ? new org.springframework.cache.support.SimpleValueWrapper(refreshed)
+ : null;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public ValueWrapper get(Object key) { | |
| String stringKey = String.valueOf(key); | |
| Object valueObject = map.get(stringKey); | |
| if (valueObject != null) { | |
| return new org.springframework.cache.support.SimpleValueWrapper(valueObject); | |
| } | |
| fieldActionsCacheService.reloadCachedFunctions(stringKey); | |
| return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey)); | |
| } | |
| @Override | |
| public ValueWrapper get(Object key) { | |
| String stringKey = String.valueOf(key); | |
| Object valueObject = map.get(stringKey); | |
| if (valueObject != null) { | |
| return new org.springframework.cache.support.SimpleValueWrapper(valueObject); | |
| } | |
| fieldActionsCacheService.reloadCachedFunctions(stringKey); | |
| Object refreshed = map.get(stringKey); | |
| return refreshed != null | |
| ? new org.springframework.cache.support.SimpleValueWrapper(refreshed) | |
| : null; | |
| } |
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
around lines 17 to 27, the get(Object) implementation currently returns new
SimpleValueWrapper(null) on misses which violates the Cache contract; modify the
method to convert the key to string, try map.get(stringKey) and if non-null
return a SimpleValueWrapper, otherwise call
fieldActionsCacheService.reloadCachedFunctions(stringKey), then re-check
map.get(stringKey) and return a SimpleValueWrapper only if that second lookup is
non-null; if still null return null (do not wrap null).
| public <T> T get(Object key, Class<T> type) { | ||
| String stringKey = String.valueOf(key); | ||
| Object valueObject = map.get(stringKey); | ||
|
|
||
| if (valueObject != null) { | ||
| return type.cast(valueObject); | ||
| } | ||
|
|
||
| fieldActionsCacheService.reloadCachedFunctions(stringKey); | ||
| return type.cast(map.get(stringKey)); | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Override typed get; mirror null semantics after reload
Add @Override and return null when still missing.
- public <T> T get(Object key, Class<T> type) {
+ @Override
+ public <T> T get(Object key, Class<T> type) {
String stringKey = String.valueOf(key);
Object valueObject = map.get(stringKey);
if (valueObject != null) {
return type.cast(valueObject);
}
fieldActionsCacheService.reloadCachedFunctions(stringKey);
- return type.cast(map.get(stringKey));
+ Object refreshed = map.get(stringKey);
+ return refreshed != null ? type.cast(refreshed) : null;
}🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
around lines 29 to 40, add the @Override annotation to the get method and change
the logic so that after calling
fieldActionsCacheService.reloadCachedFunctions(stringKey) you read the map again
into a local variable, return null if that lookup is still missing, and only
call type.cast(...) when the looked-up value is non-null to preserve the
original null semantics and avoid a ClassCastException.
...on-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
Outdated
Show resolved
Hide resolved
| Supplier<Map<String, CachedFunction>> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()); | ||
|
|
||
| caches.add(new FunctionsMapCache( | ||
| CacheMapKeys.FUNCTIONS, | ||
| functionsFactory, | ||
| fieldActionsCacheService, | ||
| petriNetService | ||
| )); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align FUNCTIONS cache with stored type from FunctionsMapCache
With FunctionsMapCache storing Function, adjust the factory’s type accordingly.
- Supplier<Map<String, CachedFunction>> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize());
+ Supplier<Map<String, Function>> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize());
@@
- caches.add(new FunctionsMapCache(
+ caches.add(new FunctionsMapCache(
CacheMapKeys.FUNCTIONS,
functionsFactory,
fieldActionsCacheService,
petriNetService
));Ensure you import com.netgrif.application.engine.objects.petrinet.domain.Function.
@@
-import com.netgrif.application.engine.workflow.domain.CachedFunction;
+import com.netgrif.application.engine.objects.petrinet.domain.Function;Note: Keep the CachedFunction import for the namespace cache below.
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
around lines 61 to 69, the Supplier factory is typed as Supplier<Map<String,
CachedFunction>> but FunctionsMapCache stores Function instances; change the
factory type to Supplier<Map<String,
com.netgrif.application.engine.objects.petrinet.domain.Function>> (and update
the MaxSizeHashMap generic accordingly) and add an import for
com.netgrif.application.engine.objects.petrinet.domain.Function; keep the
existing CachedFunction import for the namespace cache below.
| /** | ||
| * Finds and returns a single function subdocument from the {@code petriNet} collection | ||
| * by its nested {@code functions._id}. | ||
| * | ||
| * @param functionId the string form of the function's ObjectId (24-hex) | ||
| * @return the matching {@code Function} subdocument, or {@code null} if not found | ||
| * @throws IllegalArgumentException if {@code functionId} is not a valid ObjectId | ||
| */ | ||
| Function findByFunctionId(String functionId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Return nullability + index requirement for fast lookups by nested functions._id.
- Mark the return as nullable to match the “null if not found” contract.
- Add an index on petriNet.functions._id; without it, lookups will scan the whole collection at scale.
Apply near the signature:
- Function findByFunctionId(String functionId);
+ @org.springframework.lang.Nullable
+ Function findByFunctionId(String functionId);Create the index during startup (e.g., in configuration):
// Example (place in a @Configuration initializer)
mongoTemplate.indexOps("petriNet")
.ensureIndex(new org.springframework.data.mongodb.core.index.Index()
.on("functions._id", org.springframework.data.domain.Sort.Direction.ASC));Would you like me to open a follow-up for the index migration?
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
around lines 337 to 345, the method signature and docs promise "null if not
found" but the return isn't marked nullable and there is no note about indexing
for performance; update the method signature to indicate the return can be null
(use your codebase's Nullable annotation or JSR305/@Nullable) and update the
Javadoc accordingly, and add a startup configuration that ensures an ascending
index on petriNet.functions._id (create the index via your
MongoTemplate/IndexOps during application startup) so nested lookups are
efficient at scale.
| @Override | ||
| public Function findByFunctionId(String functionId) { | ||
| Query query = new Query(); | ||
|
|
||
| query.addCriteria(Criteria.where("functions._id").is(new ObjectId(functionId))); | ||
|
|
||
| PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet"); | ||
|
|
||
| Optional<Function> optionalFunction = petriNet.getFunctions().stream().filter(function -> function.getObjectId().toString().equals(functionId)).findFirst(); | ||
|
|
||
| return optionalFunction.orElse(null); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix NPE on miss and validate ObjectId; project only the matched function.
Current code NPEs when no PetriNet matches and accepts invalid ids. Also loads all functions; use elemMatch to project the matched one only.
Apply:
@@
@Override
public Function findByFunctionId(String functionId) {
- Query query = new Query();
-
- query.addCriteria(Criteria.where("functions._id").is(new ObjectId(functionId)));
-
- PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet");
-
- Optional<Function> optionalFunction = petriNet.getFunctions().stream().filter(function -> function.getObjectId().toString().equals(functionId)).findFirst();
-
- return optionalFunction.orElse(null);
+ if (functionId == null || functionId.isBlank() || !ObjectId.isValid(functionId)) {
+ throw new IllegalArgumentException("Invalid functionId: " + functionId);
+ }
+ ObjectId oid = new ObjectId(functionId);
+ Query query = new Query(Criteria.where("functions._id").is(oid));
+ // project only the matched array element to reduce payload
+ query.fields().elemMatch("functions", Criteria.where("_id").is(oid));
+
+ PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet");
+ if (petriNet == null || petriNet.getFunctions() == null || petriNet.getFunctions().isEmpty()) {
+ return null;
+ }
+ // after elemMatch the first item is the matched function
+ Function f = petriNet.getFunctions().getFirst();
+ return (f != null && oid.equals(f.getObjectId())) ? f : null;
}Committable suggestion skipped: line range outside the PR's diff.
.../src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Outdated
Show resolved
Hide resolved
.../src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Outdated
Show resolved
Hide resolved
| void reloadCachedFunctions(String petriNetId); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Document the new overload and define its contract (evict vs. rebuild).
Clarify whether this method evicts and then rebuilds, or only rebuilds. Also specify expected ID format (24-hex) and error behavior.
Apply Javadoc near the signature:
+ /**
+ * Reloads (evict + rebuild) cached functions for the given PetriNet ID.
+ * The ID must be a valid 24-hex ObjectId.
+ * @throws IllegalArgumentException if {@code petriNetId} is invalid or the PetriNet does not exist
+ */
void reloadCachedFunctions(String petriNetId);🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java
around lines 16 to 17, add Javadoc to the reloadCachedFunctions(String
petriNetId) overload that (1) states its contract clearly — whether it evicts
the existing cache entry and rebuilds it or only rebuilds in-place (choose and
document one behavior explicitly), (2) documents the expected petriNetId format
(24-character lowercase hex string) and validation behavior, (3) documents error
behavior (throw IllegalArgumentException for invalid id format,
NoSuchElementException if the referenced Petri net does not exist, and
Propagate/Log RuntimeException for rebuild failures), and (4) include any
thread-safety and performance notes (e.g., this method is blocking and may
acquire cache lock). Ensure the Javadoc is concise and placed immediately above
the method signature to define the contract unambiguously.
|
There are test errors |
| .collect(Collectors.toCollection(ArrayList::new)); | ||
|
|
||
|
|
||
| Supplier<Map<String, Closure>> actionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MaxSizeHashMap is not a thread safe collection. Multiple threads can read and write to the cache at the same time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out, I’ll fix it.
…e mutation on get or put
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
♻️ Duplicate comments (9)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (2)
21-32: Honor Cache.get contract; avoid NPE; remove unnecessary PetriNet fetch; handle contention.
- Return null on miss, not ValueWrapper(null).
- Use reload-by-id API; fetching PetriNet is unnecessary.
- Prevent thundering herd by delegating to GenericMapCache.get(key, loader).
Apply:
@Override - public ValueWrapper get(Object key) { - String stringKey = String.valueOf(key); - - Object valueObject = map().get(stringKey); - if (valueObject != null) { - return new SimpleValueWrapper(List.copyOf((List<CachedFunction>) valueObject)); - } - PetriNet petriNet = petriNetService.getPetriNet(stringKey); - fieldActionsCacheService.cachePetriNetFunctions(petriNet); - return new SimpleValueWrapper(List.copyOf((List<CachedFunction>) map().get(stringKey))); - } + public ValueWrapper get(Object key) { + if (key == null) throw new IllegalArgumentException("Cache key must not be null"); + final String stringKey = key.toString(); + List<CachedFunction> value = this.get(stringKey, () -> { + fieldActionsCacheService.reloadCachedFunctions(stringKey); + List<CachedFunction> v = (List<CachedFunction>) map().get(stringKey); + return v == null ? null : List.copyOf(v); + }); + return value == null ? null : new SimpleValueWrapper(value); + }
34-46: Override typed get; mirror null-miss semantics; avoid PetriNet fetch.Also add key validation and contention-safe loader.
Apply:
- public <T> T get(Object key, Class<T> type) { - String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); - - if (valueObject != null) { - return type.cast(List.copyOf((List<CachedFunction>) valueObject)); - } - - PetriNet petriNet = petriNetService.getPetriNet(stringKey); - fieldActionsCacheService.cachePetriNetFunctions(petriNet); - return type.cast(List.copyOf((List<CachedFunction>) map().get(stringKey))); - - } + @Override + public <T> T get(Object key, Class<T> type) { + if (key == null) throw new IllegalArgumentException("Cache key must not be null"); + final String stringKey = key.toString(); + List<CachedFunction> value = this.get(stringKey, () -> { + fieldActionsCacheService.reloadCachedFunctions(stringKey); + List<CachedFunction> v = (List<CachedFunction>) map().get(stringKey); + return v == null ? null : List.copyOf(v); + }); + return value == null ? null : type.cast(value); + }application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (3)
15-22: Parameterize the cache; align with stored type; add @OverRide imports not needed.Removes raw type usage and keeps type-safety.
Apply:
-@Slf4j -public class FunctionsMapCache extends GenericMapCache { +@Slf4j +public class FunctionsMapCache extends GenericMapCache<CachedFunction> { @@ - public FunctionsMapCache(String name, java.util.function.Supplier<Map<String, CachedFunction>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService, IGroovyShellFactory shellFactory) { - super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService); + public FunctionsMapCache(String name, + java.util.function.Supplier<Map<String, CachedFunction>> mapFactory, + IFieldActionsCacheService fieldActionsCacheService, + IPetriNetService petriNetService, + IGroovyShellFactory shellFactory) { + super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService); this.shell = shellFactory.getGroovyShell(); }
25-40: Fix miss semantics; avoid double build; use super.put; validate key.
- Return null when absent.
- Build once on miss; reuse built instance for storage and response (or consistently return a copy).
Apply:
@Override public synchronized ValueWrapper get(Object key) { - String stringKey = String.valueOf(key); + if (key == null) throw new IllegalArgumentException("Cache key must not be null"); + String stringKey = key.toString(); Object valueObject = map().get(stringKey); if (valueObject != null) { - return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject)); + return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject)); } Function function = petriNetService.findByFunctionId(stringKey); if (function != null) { - map().put(stringKey, CachedFunction.build(shell, function)); - return new SimpleValueWrapper(CachedFunction.build(shell, function)); + CachedFunction built = CachedFunction.build(shell, function); + super.put(stringKey, built); + return new SimpleValueWrapper(CachedFunction.copyOf(shell, built)); } - return new SimpleValueWrapper(null); + return null; }
42-57: Override typed get; unify semantics with untyped; return null on miss.Also avoid double build and write via super.put.
Apply:
- public synchronized <T> T get(Object key, Class<T> type) { - String stringKey = String.valueOf(key); + @Override + public synchronized <T> T get(Object key, Class<T> type) { + if (key == null) throw new IllegalArgumentException("Cache key must not be null"); + String stringKey = key.toString(); Object valueObject = map().get(stringKey); if (valueObject != null) { - return type.cast(valueObject); + return type.cast(CachedFunction.copyOf(shell, (CachedFunction) valueObject)); } Function function = petriNetService.findByFunctionId(stringKey); if (function != null) { - map().put(stringKey, CachedFunction.build(shell, function)); - return type.cast(CachedFunction.build(shell, function)); + CachedFunction built = CachedFunction.build(shell, function); + super.put(stringKey, built); + return type.cast(CachedFunction.copyOf(shell, built)); } - return type.cast(null); + return null; }application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (3)
12-12: Parameterize the cache with Closure to eliminate raw typesExtend
GenericMapCache<Closure>for compile-time safety.- public class ActionsMapCache extends GenericMapCache { + public class ActionsMapCache extends GenericMapCache<Closure> {
22-28: Honor Cache contract: return null on miss, don’t wrap nullWrapping
nullinSimpleValueWrapperviolates Spring’sCachecontract.Object valueObject = map().get(stringKey); if (valueObject != null) { return new SimpleValueWrapper(valueObject); } fieldActionsCacheService.reloadCachedFunctions(stringKey); - return new SimpleValueWrapper(map().get(stringKey)); + Object refreshed = map().get(stringKey); + return refreshed != null ? new SimpleValueWrapper(refreshed) : null;
30-41: Add @OverRide and mirror null semantics for typed getAlso avoid
type.cast(null)and returnnulldirectly.- public synchronized <T> T get(Object key, Class<T> type) { + @Override + public synchronized <T> T get(Object key, Class<T> type) { String stringKey = String.valueOf(key); Object valueObject = map().get(stringKey); if (valueObject != null) { return type.cast(valueObject); } fieldActionsCacheService.reloadCachedFunctions(stringKey); - return type.cast(map().get(stringKey)); + Object refreshed = map().get(stringKey); + return refreshed != null ? type.cast(refreshed) : null; }Run to locate similar violations across caches:
#!/bin/bash rg -nP --type=java 'SimpleValueWrapper\\(null\\)|return\\s+type\\.cast\\(null\\)'application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
17-18: Tighten generics: use Class and make it finalReduces unchecked casts and encodes the value type at the compiler level.
- protected Class<?> valueType; + protected final Class<V> valueType; @@ - public GenericMapCache(String name, Class<?> valueType, java.util.function.Supplier<Map<String, V>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + public GenericMapCache(String name, Class<V> valueType, java.util.function.Supplier<Map<String, V>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) {Also applies to: 26-26
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
Slf4j(11-42)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
Slf4j(14-53)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(14-104)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
Slf4j(11-42)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1)
Slf4j(14-58)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
Slf4j(14-53)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1)
Slf4j(14-58)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (1)
Slf4j(14-53)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(14-104)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java (1)
Slf4j(11-42)application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java (1)
Slf4j(14-58)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(14-104)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Test
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
53-82: Good: bounded maps wrapped with synchronizationWrapping
MaxSizeHashMapwithCollections.synchronizedMapaddresses immediate thread-safety concerns for shared caches.
| @Override | ||
| public synchronized ValueWrapper get(Object key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix return type reference: unresolved ValueWrapper
ValueWrapper is an inner type of org.springframework.cache.Cache; reference it properly to fix compilation.
- public synchronized ValueWrapper get(Object key) {
+ public synchronized org.springframework.cache.Cache.ValueWrapper get(Object key) {Add this import outside the shown range (either one of the two):
- import org.springframework.cache.Cache;
- or import org.springframework.cache.Cache.ValueWrapper;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public synchronized ValueWrapper get(Object key) { | |
| @Override | |
| public synchronized org.springframework.cache.Cache.ValueWrapper get(Object key) { |
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java
around lines 18-19, the method signature uses ValueWrapper but the type is
unresolved because ValueWrapper is an inner type of
org.springframework.cache.Cache; fix by adding an import for the inner type
(import org.springframework.cache.Cache.ValueWrapper;) or import the outer type
(import org.springframework.cache.Cache;) and reference the type explicitly as
Cache.ValueWrapper in the signature; add the chosen import at the top of the
file (outside the shown range) so the method compiles.
...on-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
Show resolved
Hide resolved
| Supplier<Map<String, Closure>> actionsFactory = | ||
| () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize())); | ||
|
|
||
| caches.add(new ActionsMapCache( | ||
| CacheMapKeys.ACTIONS, | ||
| actionsFactory, | ||
| fieldActionsCacheService, | ||
| petriNetService | ||
| )); | ||
|
|
||
| Supplier<Map<String, CachedFunction>> functionsFactory = | ||
| () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize())); | ||
|
|
||
| caches.add(new FunctionsMapCache( | ||
| CacheMapKeys.FUNCTIONS, | ||
| functionsFactory, | ||
| fieldActionsCacheService, | ||
| petriNetService, | ||
| groovyShellFactory | ||
| )); | ||
|
|
||
| Supplier<Map<String, List<CachedFunction>>> nsFactory = | ||
| () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize())); | ||
|
|
||
| caches.add(new FunctionsNamespaceMapCache( | ||
| CacheMapKeys.NAMESPACE_FUNCTIONS, | ||
| nsFactory, | ||
| fieldActionsCacheService, | ||
| petriNetService | ||
| )); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider Caffeine for production-grade bounded caches
Caffeine gives O(1) concurrency, admission/eviction policies, and metrics; wire CaffeineCache instances into SimpleCacheManager as a follow-up.
| Object valueObject = map().get(stringKey); | ||
| if (valueObject != null) { | ||
| return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider eliminating per-call copyOf for performance.
Re-evaluating Groovy on every hit undermines caching. If thread-safety allows, return the stored CachedFunction directly, or cache compiled classes and rehydrate closures cheaply. If you must copy, consider Closure#rehydrate over full re-eval.
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java
around lines 28-31, the code currently calls CachedFunction.copyOf(...) on every
cache hit which forces Groovy re-evaluation and kills performance; instead, if
CachedFunction is immutable or can be made thread-safe, return the stored
CachedFunction directly from the cache; otherwise change the cache to store a
compiled/rehydratable Closure or class and create lightweight per-call wrappers
via Closure.rehydrate (or a cached compiled form) rather than full re-eval, or
implement copy-on-write so copies are made only on mutating calls — pick one
approach, make CachedFunction immutable or mark it thread-safe, update the cache
return to return the stored instance when safe, or update the cache value to a
compiled/rehydratable closure and create lightweight closures on read.
| public class FunctionsNamespaceMapCache extends GenericMapCache { | ||
|
|
||
| public FunctionsNamespaceMapCache(String name, java.util.function.Supplier<Map<String, List<CachedFunction>>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { | ||
| super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Parameterize the cache type; avoid raw GenericMapCache.
Prevents unsafe casts and lets GenericMapCache.safeCast help.
Apply:
-@Slf4j
-public class FunctionsNamespaceMapCache extends GenericMapCache {
+@Slf4j
+public class FunctionsNamespaceMapCache extends GenericMapCache<List<CachedFunction>> {
@@
- public FunctionsNamespaceMapCache(String name, java.util.function.Supplier<Map<String, List<CachedFunction>>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) {
- super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService);
- }
+ public FunctionsNamespaceMapCache(String name,
+ java.util.function.Supplier<Map<String, List<CachedFunction>>> mapFactory,
+ IFieldActionsCacheService fieldActionsCacheService,
+ IPetriNetService petriNetService) {
+ super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService);
+ }🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java
around lines 15–19, the class currently extends the raw GenericMapCache which
causes unsafe casts; change the class to extend GenericMapCache<Map<String,
List<CachedFunction>>> and update the constructor signature to accept
Supplier<Map<String, List<CachedFunction>>>; in the super(...) call replace the
raw List.class with a properly typed Class for Map (use
GenericMapCache.safeCast(Map.class) or an equivalent safeCast to produce a
Class<Map<String, List<CachedFunction>>>), ensuring all generics line up so no
raw types remain and unsafe casting is avoided.
| @Override | ||
| public void put(Object key, Object value) { | ||
| String k = String.valueOf(key); | ||
| map().put(k, List.copyOf((List<CachedFunction>) value)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Use super.put and enforce immutability once.
Avoid bypassing safeCast; still store an unmodifiable List.
Apply:
@Override
public void put(Object key, Object value) {
- String k = String.valueOf(key);
- map().put(k, List.copyOf((List<CachedFunction>) value));
+ if (key == null) throw new IllegalArgumentException("Cache key must not be null");
+ super.put(key, List.copyOf((List<CachedFunction>) value));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public void put(Object key, Object value) { | |
| String k = String.valueOf(key); | |
| map().put(k, List.copyOf((List<CachedFunction>) value)); | |
| } | |
| @Override | |
| public void put(Object key, Object value) { | |
| if (key == null) throw new IllegalArgumentException("Cache key must not be null"); | |
| super.put(key, List.copyOf((List<CachedFunction>) value)); | |
| } |
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java
around lines 48-52, the current implementation bypasses safeCast and writes
directly to map(); change it to call super.put(key, value) and ensure
immutability exactly once: use the class's safeCast (or equivalent) to cast the
incoming value to List<CachedFunction>, wrap that result with List.copyOf(...)
to make it unmodifiable, and pass that wrapped list into super.put(key,
wrappedList) instead of map().put(...).
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Outdated
Show resolved
Hide resolved
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Outdated
Show resolved
Hide resolved
| public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) { | ||
| if (cachedFunction == null) { | ||
| return null; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Guard against null input early (returning null here may mask bugs).
Prefer failing fast on null input or document the nullable contract. I recommend throwing IllegalArgumentException for null cachedFunction to avoid silent nulls propagating.
Apply:
- public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) {
- if (cachedFunction == null) {
- return null;
- }
+ public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) {
+ if (cachedFunction == null) {
+ throw new IllegalArgumentException("cachedFunction must not be null");
+ }🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java
around lines 27 to 31, the copyOf method currently returns null when
cachedFunction is null which can hide errors; change the method to fail fast by
throwing an IllegalArgumentException when cachedFunction is null (with a clear
message like "cachedFunction must not be null") instead of returning null so
callers immediately see the contract violation.
| Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition()); | ||
| if (code == null) { | ||
| throw new IllegalArgumentException("Non compilable function"); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden evaluation: null/blank definition, non-Closure result, and exceptions.
Current code may NPE on null definition and ClassCastException on non-Closure. Wrap compilation errors consistently.
Apply:
- Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition());
- if (code == null) {
- throw new IllegalArgumentException("Non compilable function");
- }
+ String def = cachedFunction.getFunction() != null ? cachedFunction.getFunction().getDefinition() : null;
+ if (def == null || def.isBlank()) {
+ throw new IllegalArgumentException("Function definition must not be null/blank");
+ }
+ final Object evaluated;
+ try {
+ evaluated = shell.evaluate(def);
+ } catch (Throwable t) {
+ throw new IllegalArgumentException("Non compilable function", t);
+ }
+ if (!(evaluated instanceof Closure)) {
+ throw new IllegalArgumentException("Function definition did not evaluate to a Closure");
+ }
+ Closure code = (Closure) evaluated;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition()); | |
| if (code == null) { | |
| throw new IllegalArgumentException("Non compilable function"); | |
| } | |
| String def = cachedFunction.getFunction() != null | |
| ? cachedFunction.getFunction().getDefinition() | |
| : null; | |
| if (def == null || def.isBlank()) { | |
| throw new IllegalArgumentException("Function definition must not be null/blank"); | |
| } | |
| final Object evaluated; | |
| try { | |
| evaluated = shell.evaluate(def); | |
| } catch (Throwable t) { | |
| throw new IllegalArgumentException("Non compilable function", t); | |
| } | |
| if (!(evaluated instanceof Closure)) { | |
| throw new IllegalArgumentException("Function definition did not evaluate to a Closure"); | |
| } | |
| Closure code = (Closure) evaluated; |
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java
around lines 32 to 36, harden the evaluation: first validate
cachedFunction.getFunction().getDefinition() for null or blank and throw
IllegalArgumentException with a clear message if invalid; then call
shell.evaluate(...) inside a try/catch to catch and wrap any compilation/runtime
exceptions into an IllegalArgumentException that includes the original
exception; after evaluation verify the returned object is a Closure (not null or
another type) and if not throw IllegalArgumentException indicating non-Closure
result (including actual result type when available).
…namespaceFunctionsCache from local Map to Redis
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (8)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
59-61: Fix compile error: qualify ValueRetrievalExceptionIt’s an inner type of Cache.
- } catch (Exception ex) { - throw new ValueRetrievalException(stringKey, loader, ex); + } catch (Exception ex) { + throw new Cache.ValueRetrievalException(stringKey, loader, ex); }application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (6)
58-65: Fail fast on missing namespace functions cacheUse a required-cache helper to avoid NPEs and produce clear errors.
- Cache namespaceFunctionsCache = cacheManager.getCache(properties.getNamespaceFunctions()); + Cache namespaceFunctionsCache = getRequiredCache(properties.getNamespaceFunctions());
149-151: Return a typed copy of the namespace cache; avoid raw castsSafer and future‑proof.
- return new HashMap<>((Map) cacheManager.getCache(properties.getNamespaceFunctions()).getNativeCache()); + @SuppressWarnings("unchecked") + Map<String, List<CachedFunction>> nativeMap = + (Map<String, List<CachedFunction>>) getRequiredCache(properties.getNamespaceFunctions()).getNativeCache(); + return new HashMap<>(nativeMap);
154-166: Clear caches via required-cache helperPrevents NPEs if a cache is missing.
- cacheManager.getCache(CacheMapKeys.ACTIONS).clear(); + getRequiredCache(CacheMapKeys.ACTIONS).clear(); @@ - cacheManager.getCache(properties.getNamespaceFunctions()).clear(); + getRequiredCache(properties.getNamespaceFunctions()).clear(); @@ - cacheManager.getCache(CacheMapKeys.FUNCTIONS).clear(); + getRequiredCache(CacheMapKeys.FUNCTIONS).clear();
69-72: Guard reload path with required-cache helperPrevents NPE when cache is misconfigured.
- cacheManager.getCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId); + getRequiredCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId); cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId));
80-92: Use atomic Cache#get(key, loader) and evict on rewrite; stop peeking into native mapThis fixes potential NPE and races, and honors rewrite semantics.
- Cache actionsCache = cacheManager.getCache(CacheMapKeys.ACTIONS); - Object nativeActionsCache = actionsCache.getNativeCache(); - - if (nativeActionsCache instanceof Map<?, ?> map) { - if (shouldRewriteCachedActions || !map.containsKey(stringId) ) { - Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); - actionsCache.put(stringId, code); - } - } - return (Closure) actionsCache.get(stringId).get(); + Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS); + if (shouldRewriteCachedActions) { + actionsCache.evict(stringId); + } + return actionsCache.get(stringId, () -> (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"));
95-107: Compute functions via Cache API, no native map castingCleaner, thread-safe, and backend-agnostic.
- Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS); - Object nativeFunctionsCache = functionsCache.getNativeCache(); - - if (nativeFunctionsCache instanceof Map<?, ?> map) { - functions.forEach(function -> { - if (!map.containsKey(function.getStringId())) { - functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); - } - cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); - }); - } + Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); + for (Function function : functions) { + CachedFunction cf = functionsCache.get(function.getStringId(), + () -> CachedFunction.build(shell, function)); + cachedFunctions.add(cf); + }application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
38-42: Avoid duplicate registrations for specialized cachesFilter out ACTIONS and FUNCTIONS from the generic cache list before adding specialized implementations.
- Set<String> cacheNames = properties.getAllCaches(); - List<Cache> caches = cacheNames.stream() + Set<String> cacheNames = new LinkedHashSet<>(properties.getAllCaches()); + cacheNames.removeAll(Set.of(CacheMapKeys.ACTIONS, CacheMapKeys.FUNCTIONS)); + List<Cache> caches = cacheNames.stream() .map(ConcurrentMapCache::new) .collect(Collectors.toCollection(ArrayList::new));
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (5)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-6)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-6)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(12-122)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
Slf4j(66-581)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: task-list-completed
- GitHub Check: Test
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (1)
44-48: LGTM on adding namespaceFunctions property and wiring it into getAllCaches()Also applies to: 62-63
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java
Outdated
Show resolved
Hide resolved
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Outdated
Show resolved
Hide resolved
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Show resolved
Hide resolved
.../src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Show resolved
Hide resolved
…e to changes in cache structure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
PR: netgrif/application-engine#334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: Build
🔇 Additional comments (4)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (2)
567-567: No functional change here.Whitespace-only change; nothing to address.
8-8: Cache names match — no action required.
@Cacheable values in PetriNetService ("petriNetCache" — line 158; "petriNetById" — 250; "petriNetByIdentifier" — 261; "petriNetNewest" — 284) exactly match defaults in CacheConfigurationProperties (lines 22, 27, 32, 37).application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
3-7: LGTM — constants holder is correctly non-instantiable.
Matches prior guidance: final class + private ctor; fields are public static final. No issues.application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
33-39: No action required — factories already return synchronized maps. CacheConfiguration creates GenericMapCache instances with Suppliers that wrap MaxSizeHashMap via Collections.synchronizedMap (application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java:45–58), so the current unsynchronized reads are safe.
| @Override public Object getNativeCache() { return Map.copyOf(map()); } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Preserve native cache identity; clear in place.
Returning Map.copyOf breaks “native” semantics and reassigning the map on clear invalidates existing nativeCache holders. Return the backing map and clear it in place.
Apply this diff:
- @Override public Object getNativeCache() { return Map.copyOf(map()); }
+ @Override public Object getNativeCache() { return map(); }
@@
- public synchronized void clear() {
- this.atomicMapRef.set(mapFactory.get());
- }
+ public synchronized void clear() {
+ map().clear();
+ log.debug("{} cache cleared", name);
+ }Also applies to: 95-98
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
around lines 30-31 (and likewise adjust lines 95-98), the getNativeCache method
currently returns Map.copyOf(map()) which breaks native cache identity; change
getNativeCache to return the backing map directly (return map()) so callers keep
the same instance. Also modify clear logic (lines ~95-98) to clear the existing
backing map in place (call map().clear()) instead of reassigning a new map
instance, preserving nativeCache holders and expected semantics.
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Show resolved
Hide resolved
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Show resolved
Hide resolved
...on-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
Outdated
Show resolved
Hide resolved
renczesstefan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review coderabbitAI comments.
| private final CacheConfigurationProperties properties; | ||
|
|
||
| public CacheConfiguration(CacheConfigurationProperties properties) { | ||
| public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its only cosmetic, but instead of this constructor you can use @RequiredArgsConstructor over the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. I will fix that.
.../src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java (1)
63-107: Consider removing commented-out code or creating a tracked issue.The TODO comment on already commented-out code (44 lines) is a code smell. If the clone method is not needed, remove it now rather than deferring the decision. If there's genuine uncertainty, create a proper JIRA ticket to track the evaluation.
Would you like me to help evaluate whether this clone method is used anywhere in the codebase, or would you prefer to remove it immediately and rely on the superclass implementation?
🔎 If the clone method is confirmed as unused, apply this diff to remove the dead code:
- // todo: delete clone method if not needed -// @Override -// public PetriNet clone() { -// PetriNet clone = new PetriNet(); -// clone.setIdentifier(this.getIdentifier()); -// clone.setUriNodeId(this.getUriNodeId()); -// clone.setInitials(this.getInitials()); -// clone.setTitle(this.getTitle().clone()); -// clone.setDefaultRoleEnabled(this.isDefaultRoleEnabled()); -// clone.setDefaultCaseName(this.getDefaultCaseName() == null ? null : this.getDefaultCaseName().clone()); -// clone.setDefaultCaseNameExpression(this.getDefaultCaseNameExpression() == null ? null : this.getDefaultCaseNameExpression().clone()); -// clone.setIcon(this.getIcon()); -// clone.setCreationDate(this.getCreationDate()); -// clone.setVersion(this.getVersion() == null ? null : this.getVersion().clone()); -// clone.setAuthor(this.getAuthor() == null ? null : this.getAuthor().clone()); -// clone.setTransitions(this.getTransitions() == null ? null : this.getTransitions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new com.netgrif.application.engine.adapter.spring.petrinet.domain.Transition(e.getValue()), (v1, v2) -> v1, LinkedHashMap::new))); -// clone.setRoles(this.roles == null ? null : this.roles.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new com.netgrif.application.engine.adapter.spring.petrinet.domain.roles.ProcessRole(e.getValue()), (v1, v2) -> v1, LinkedHashMap::new))); -// clone.setTransactions(this.getTransactions() == null ? null : this.getTransactions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new com.netgrif.application.engine.adapter.spring.petrinet.domain.Transaction(e.getValue()), (v1, v2) -> v1, LinkedHashMap::new))); -// clone.setImportXmlPath(this.getImportXmlPath()); -// clone.setImportId(this.importId); -// clone.setObjectId(this._id); -// clone.setDataSet(this.getDataSet().entrySet() -// .stream() -// .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone(), (x, y)->y, LinkedHashMap::new)) -// ); -// clone.setPlaces(this.getPlaces().entrySet() -// .stream() -// .collect(Collectors.toMap(Map.Entry::getKey, e -> new Place(e.getValue()), (x, y)->y, LinkedHashMap::new)) -// ); -// clone.setArcs(this.getArcs().entrySet() -// .stream() -// .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream() -// .map(Arc::clone) -// .collect(Collectors.toList()), (x,y)->y, LinkedHashMap::new)) -// ); -// clone.initializeArcs(); -// clone.setCaseEvents(this.getCaseEvents() == null ? null : this.getCaseEvents().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone()))); -// clone.setProcessEvents(this.getProcessEvents() == null ? null : this.getProcessEvents().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone()))); -// clone.setPermissions(this.getPermissions() == null ? null : this.getPermissions().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new HashMap<>(e.getValue())))); -// clone.setUserRefs(this.getUserRefs() == null ? null : this.getUserRefs().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new HashMap<>(e.getValue())))); -// this.getNegativeViewRoles().forEach(clone::addNegativeViewRole); -// this.getFunctions().forEach(clone::addFunction); -// clone.setTags(new HashMap<>(this.getTags())); -// return clone; -// }
♻️ Duplicate comments (8)
application-engine/src/main/resources/application.yaml (1)
119-121: Critical security risk: All actuator endpoints publicly accessible.Line 121 exposes all actuator endpoints (
include: "*"), and line 89 permits anonymous access to/manage/**. This combination makes sensitive endpoints (env,heapdump,beans,caches, etc.) publicly accessible without authentication.This concern was previously raised and includes recommended fixes to restrict exposure to only required endpoints and remove or secure
/manage/**from anonymous access.application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)
4-4: Avoid wildcard imports; restore explicit imports.Wildcard imports violate common style guidelines, can hide accidental type collisions, and make IDE organize-imports unstable. Please use explicit imports for the actually used types (PetriNet, PetriNetSearch, Transition, VersionType).
🔎 Apply this diff:
-import com.netgrif.application.engine.objects.petrinet.domain.*; +import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; +import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; +import com.netgrif.application.engine.objects.petrinet.domain.Transition; +import com.netgrif.application.engine.objects.petrinet.domain.VersionType;Based on past review comments, wildcard imports should be avoided in this codebase.
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java (1)
16-17: Add Javadoc for the newreloadCachedFunctions(String petriNetId)overload.This method lacks documentation specifying its contract, expected ID format, and error behavior. As noted in a previous review, clarify whether it evicts and rebuilds or only rebuilds, and document the expected format and error handling.
📝 Apply suggested Javadoc
+ /** + * Reloads (evicts and rebuilds) cached functions for the PetriNet with the given ID. + * + * @param petriNetId the MongoDB ObjectId (24-character hex string) of the PetriNet + * @throws IllegalArgumentException if the ID format is invalid or the PetriNet does not exist + */ void reloadCachedFunctions(String petriNetId);application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (2)
90-93:clear()replaces the map reference instead of clearing in-place.This approach has trade-offs:
- Pro: Ongoing operations on the old map complete without interference
- Con: Any code holding a reference to
getNativeCache()(if it returned the backing map) would have a stale referenceSince
getNativeCache()returns a copy, this is less problematic, but it's inconsistent with typical cache semantics where clearing affects the same backing store.
31-31:getNativeCache()returns a defensive copy, breaking the Spring Cache contract.The Spring Cache contract expects
getNativeCache()to return the actual backing store. ReturningMap.copyOf()means callers (likeFieldActionsCacheService.getGlobalFunctionsCache()) receive a snapshot that doesn't reflect subsequent changes, and theinstanceof Mapchecks ingetCompiledAction/getCachedFunctionsoperate on a detached copy.If defensive copying is intentional for safety, document this deviation. Otherwise, return the backing map directly.
🔎 Suggested fix:
- @Override public @NotNull Object getNativeCache() { return Map.copyOf(map()); } + @Override public @NotNull Object getNativeCache() { return map(); }application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
97-112: Same TOCTOU race and NPE risk ingetCachedFunctions.Similar to
getCompiledAction, this method checkscontainsKeyon a snapshot (Map.copyOf) but operates on the actual cache. UseCache#get(key, Callable)for atomic operations:🔎 Suggested fix:
@Override public List<CachedFunction> getCachedFunctions(List<Function> functions) { List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size()); Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); - Object nativeFunctionsCache = functionsCache.getNativeCache(); - - if (nativeFunctionsCache instanceof Map<?, ?> map) { - functions.forEach(function -> { - if (!map.containsKey(function.getStringId())) { - functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); - } - cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); - }); - } + for (Function function : functions) { + CachedFunction cf = functionsCache.get(function.getStringId(), + () -> CachedFunction.build(shell, function)); + cachedFunctions.add(cf); + } return cachedFunctions; }
82-95: TOCTOU race and potential NPE ingetCompiledAction.Two issues:
TOCTOU race:
getNativeCache()returnsMap.copyOf()(a snapshot), so!map.containsKey(stringId)checks a stale copy whileactionsCache.put()writes to the actual cache. Under concurrent access, multiple threads may redundantly compile the same action.Potential NPE at line 94: If the
instanceofcheck fails (cache impl changed), the method falls through without populating the cache, thenactionsCache.get(stringId).get()returnsnull.get().Use the
Cache#get(key, Callable)API for atomic compute-if-absent:🔎 Suggested fix:
@Override public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) { String stringId = action.getId().toString(); Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS); - Object nativeActionsCache = actionsCache.getNativeCache(); - - if (nativeActionsCache instanceof Map<?, ?> map) { - if (shouldRewriteCachedActions || !map.containsKey(stringId) ) { - Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); - actionsCache.put(stringId, code); - } + if (shouldRewriteCachedActions) { + actionsCache.evict(stringId); } - return (Closure) actionsCache.get(stringId).get(); + return actionsCache.get(stringId, () -> (Closure) shell.evaluate("{-> " + action.getDefinition() + "}")); }application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
36-62: Potential duplicate cache registration ifadditionalcaches include "actions" or "functions".The code creates
ConcurrentMapCacheinstances for all names fromproperties.getAllCaches(), then addsGenericMapCacheinstances forCacheMapKeys.ACTIONSandCacheMapKeys.FUNCTIONS. If a user configuresadditionalcaches containing "actions" or "functions",SimpleCacheManagerwill have duplicate cache names with different implementations.Consider filtering out the specialized cache names before mapping:
🔎 Suggested fix:
public CacheManager cacheManager() { Set<String> cacheNames = properties.getAllCaches(); + cacheNames.remove(CacheMapKeys.ACTIONS); + cacheNames.remove(CacheMapKeys.FUNCTIONS); List<Cache> caches = cacheNames.stream() .map(ConcurrentMapCache::new) .collect(Collectors.toCollection(ArrayList::new));
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (44)
application-engine/pom.xml(0 hunks)application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy(1 hunks)application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/FieldActionsRunner.groovy(1 hunks)application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy(2 hunks)application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java(3 hunks)application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java(3 hunks)application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java(8 hunks)application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java(4 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java(1 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java(2 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java(3 hunks)application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java(1 hunks)application-engine/src/main/resources/application.yaml(1 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy(1 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy(2 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy(1 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy(2 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy(1 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy(1 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy(5 hunks)application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy(1 hunks)application-engine/src/test/resources/change_caseref_value_action_test.xml(2 hunks)application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml(1 hunks)application-engine/src/test/resources/petriNets/process_version_1_0_0.xml(1 hunks)application-engine/src/test/resources/petriNets/process_version_2_0_0.xml(1 hunks)application-engine/src/test/resources/petriNets/process_version_3_0_0.xml(1 hunks)application-engine/src/test/resources/petriNets/process_version_4_0_0.xml(1 hunks)application-engine/src/test/resources/petriNets/process_version_5_0_0.xml(1 hunks)nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java(3 hunks)nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/FunctionScope.java(1 hunks)nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java(6 hunks)nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java(2 hunks)nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.java(1 hunks)
💤 Files with no reviewable changes (1)
- application-engine/pom.xml
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xmlapplication-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.javaapplication-engine/src/test/resources/petriNets/process_version_5_0_0.xmlapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/test/resources/petriNets/process_version_1_0_0.xmlapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovynae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.javaapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovynae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/test/resources/petriNets/process_version_2_0_0.xmlapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.javaapplication-engine/src/test/resources/petriNets/process_version_3_0_0.xmlapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovynae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/test/resources/change_caseref_value_action_test.xmlapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
📚 Learning: 2025-12-12T13:40:00.786Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java:98-104
Timestamp: 2025-12-12T13:40:00.786Z
Learning: In application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java, the permission constraint logic in buildQuery() intentionally applies negative actor constraints globally: ((roleConstraint AND NOT negRoleConstraint) OR actorConstraint) AND NOT negActorConstraint. This means users in negativeViewActors are excluded even if they have positive role permissions, which is the intended authorization behavior.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
📚 Learning: 2025-12-12T12:36:06.651Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/resources/petriNets/engine-processes/org_group.xml:36-39
Timestamp: 2025-12-12T12:36:06.651Z
Learning: In the org_group.xml file (application-engine/src/main/resources/petriNets/engine-processes/org_group.xml), variable names in action blocks like userField and user_selection should be kept as-is even when the field type changes from "user" to "actor".
Applied to files:
application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xmlapplication-engine/src/test/resources/petriNets/process_version_5_0_0.xmlapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/test/resources/petriNets/process_version_4_0_0.xmlapplication-engine/src/test/resources/petriNets/process_version_1_0_0.xmlapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/test/resources/petriNets/process_version_2_0_0.xmlapplication-engine/src/test/resources/petriNets/process_version_3_0_0.xmlapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/test/resources/change_caseref_value_action_test.xmlapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy
📚 Learning: 2025-09-05T10:21:54.893Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 350
File: application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy:135-135
Timestamp: 2025-09-05T10:21:54.893Z
Learning: In ExportServiceTest.groovy, writing to src/test/resources is intentional to simulate production behavior where the working tree is mutated during file exports. This mirrors how the system works in production.
Applied to files:
application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xmlapplication-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
📚 Learning: 2025-12-12T12:37:58.852Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy:4-4
Timestamp: 2025-12-12T12:37:58.852Z
Learning: In tests under application-engine/src/test, GroupService.getDefaultSystemGroup() lazily creates the default system group if it doesn't exist. Tests can rely on this behavior and omit explicit existence/setup checks for the default system group.
Applied to files:
application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy
📚 Learning: 2025-12-12T12:40:25.863Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy:18-20
Timestamp: 2025-12-12T12:40:25.863Z
Learning: In Groovy/Java, if a parameter or collection expects ActorFieldValue, you can pass UserFieldValue (a subclass) due to polymorphism. In tests, verify that ActorListFieldValue accepts ActorFieldValue instances and that passing UserFieldValue instances is valid without casts. This pattern applies to similar Groovy files across the repository.
Applied to files:
application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/FieldActionsRunner.groovy
📚 Learning: 2025-09-29T10:31:57.325Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java:513-529
Timestamp: 2025-09-29T10:31:57.325Z
Learning: PetriNet.getStringId() returns a simple ObjectId string representation (_id.toString()), not a composite Netgrif ID format, so new ObjectId(petriNetId) works correctly when petriNetId comes from PetriNet.getStringId().
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovyapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-09-29T10:31:31.469Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java:43-45
Timestamp: 2025-09-29T10:31:31.469Z
Learning: In the Netgrif application engine, when Authentication objects reach controller endpoints, auth.getPrincipal() always returns a LoggedUser instance and is never null, so defensive casting checks are not necessary in controller methods.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-07-29T17:19:18.300Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.300Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using Lazy setter injection rather than constructor injection. This pattern with Lazy Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.
Applied to files:
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/petrinet/domain/PetriNet.javanae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-06-23T13:30:13.096Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 318
File: nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java:60-62
Timestamp: 2025-06-23T13:30:13.096Z
Learning: In MenuItemConstants enum in nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java, the field `attributeId` will be renamed to `value` to make it more generic and appropriate for both dataset attribute identifiers and technical constants like PATH_SEPARATOR.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
🧬 Code graph analysis (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java (1)
ActorTransformer(10-131)
🪛 GitHub Actions: Pull Request
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java
[error] 29-29: Compilation error: cannot find symbol 'GLOBAL' in Scope used by FunctionFactory.java.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (51)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java (2)
71-78: LGTM!The
isHigherThanconvenience method correctly delegates tocompareToand improves code readability. The implementation and documentation are both accurate.
80-87: LGTM!The
isLowerThanconvenience method correctly delegates tocompareToand provides a readable alternative to direct comparisons. The implementation and documentation are both accurate.application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy (1)
94-96: Clarify the need for bothclearFunctionCache()andclearGlobalFunctionCache()in test cleanup.These methods clear distinct caches:
clearGlobalFunctionCache()clears global-scoped functions (keyed by petriNetId), whileclearFunctionCache()clears individual cached functions. Verify both are necessary for proper test isolation, and if so, add a brief comment explaining their separate purposes.application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml (1)
220-220: No action needed—change is correct and aligns with codebase migration.The method
getNewestVersionByIdentifierhas been removed project-wide. The change togetActiveVersionByIdentifieris the only available option and is already used throughout the test suite (ExportServiceTest.groovy, ImporterTest.groovy, and production code in WorkflowService, MenuImportExportService, FilterImportExportService). This change is necessary and correct.nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/FunctionScope.java (2)
3-7: LGTM: Clear enum-level documentation.The JavaDoc provides a concise description of the enum's purpose.
10-18: Deprecation handled: FunctionScope.NAMESPACE replaced with GLOBAL and automatically migrated.The NAMESPACE enum constant has been replaced with GLOBAL in the FunctionScope enum. However, this change is managed through automatic deprecation handling, not an uncontrolled breaking change:
- Automatic migration: FunctionFactory.checkDeprecatedAttributes() (lines 26–30) automatically converts any imported function with scope="namespace" to GLOBAL and logs a deprecation warning.
- Test data compatibility: Existing test XML files (function_res.xml, function_test.xml, etc.) still use scope="namespace" and are transparently converted during import.
- Code migration: No remaining FunctionScope.NAMESPACE references in the codebase—already removed.
Minor improvements:
- Add @deprecated annotation for explicit API visibility
- Document this change in CHANGELOG for v7.0.0
- Include migration timeline in deprecation warning
External consumers calling fromValue("namespace") directly (rather than importing XML) will receive IllegalArgumentException, which is expected behavior for removed enum values.
Likely an incorrect or invalid review comment.
application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy (1)
133-133: LGTM! Test updated to use active version API.The test correctly uses the new
getActiveVersionByIdentifiermethod to verify imported PetriNets, consistent with the API changes in this PR.Also applies to: 207-207
application-engine/src/test/resources/change_caseref_value_action_test.xml (1)
32-32: LGTM! Test resource updated consistently.The embedded scripts correctly use the new
getActiveVersionByIdentifiermethod, maintaining consistency with the API changes.Also applies to: 46-46
application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy (1)
131-131: LGTM! Helper methods updated to use active version.Both
upsertNetandimportProcessOncecorrectly usegetActiveVersionByIdentifierto check for existing PetriNets before creating new ones, which aligns with the intended semantics of these operations.Also applies to: 252-252
application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy (1)
314-314: LGTM! Test updated to use active version API.The test helper correctly uses
getActiveVersionByIdentifierto retrieve the impersonation config PetriNet, consistent with the API changes. Note that this test is currently disabled.application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java (1)
427-427: LGTM! Filter creation updated to use active version.The method correctly retrieves the active version of the "filter" PetriNet during filter case creation, which is appropriate for startup initialization logic.
application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java (1)
42-42: The usage ofgetActiveVersionByIdentifierfor impersonation configs is correct and consistent with the codebase pattern. The active version semantics are appropriate here—all similar system and configuration processes (dashboard management, filters, menu, preferences) consistently use this method to retrieve the currently deployed version. The test suite already expects this behavior, and there is no pattern of using "newest" versions for system configurations at runtime.application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)
153-166: Document the semantic difference between "active" and "latest" versions.The API now provides both
getActiveVersionByIdentifier(replacinggetNewestVersionByIdentifier) andgetLatestVersionByIdentifier. These methods should be documented to clarify:
- What makes a version "active" vs "latest"
- When callers should use one vs the other
- Whether they can return different results, and if so, under what conditions
This distinction is important for correct usage throughout the codebase, particularly given the replacement of "newest" with "active."
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1)
59-59: Breaking change: Configuration property structure refactored.Renaming this field is part of a broader configuration structure migration. The external configuration changed from the old
nae.field-runner.cache-size.*prefix structure (e.g.,nae.field-runner.cache-size.namespace-functions) to the newnetgrif.engine.runner.field-runner.*structure with renamed properties (e.g.,netgrif.engine.runner.field-runner.globalFunctionsCacheSize). Existing configurations using the old property names will no longer work.Likely an incorrect or invalid review comment.
application-engine/src/test/resources/petriNets/process_version_1_0_0.xml (1)
1-9: LGTM!The XML structure is well-formed and correctly defines a test PetriNet resource for version 1.0.0, aligning with the active-version testing approach introduced in this PR.
application-engine/src/test/resources/petriNets/process_version_5_0_0.xml (1)
1-9: LGTM!The XML structure is well-formed and correctly defines a test PetriNet resource for version 5.0.0.
application-engine/src/test/resources/petriNets/process_version_3_0_0.xml (1)
1-9: LGTM!The XML structure is well-formed and correctly defines a test PetriNet resource for version 3.0.0.
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java (1)
269-272: Good defensive coding with the null check.The addition of the null check for the active filter PetriNet prevents potential NPE in subsequent operations. The switch from newest to active version aligns with the PR's objectives.
application-engine/src/test/resources/petriNets/process_version_2_0_0.xml (1)
1-9: LGTM!The XML structure is well-formed and correctly defines a test PetriNet resource for version 2.0.0.
nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java (1)
27-27: LGTM!The
versionActivefield is properly added, initialized in the constructor, and updated in theupdatemethod. This correctly mirrors the PetriNet's active-version state in the Elastic representation.Also applies to: 43-43, 52-52
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (1)
288-288: Verify which version method was previously used for case creation.The review comment references a change from
getNewestVersionByIdentifiertogetActiveVersionByIdentifier, butgetNewestVersionByIdentifierdoes not exist in the codebase. The codebase containsgetLatestVersionByIdentifier(which retrieves the highest version number) andgetActiveVersionByIdentifier(which retrieves the version marked active). Clarify whether the change is fromgetLatestVersionByIdentifieror another method.The underlying concern about semantic differences between active and latest versions remains valid:
getActiveVersionByIdentifierreturns versions with an active flag, whilegetLatestVersionByIdentifierreturns versions sorted by version number. If the change is confirmed, verify that all callers (FilterImportExportService, MenuItemService, ActionApiImpl) are aware of this distinction and that test coverage includes scenarios where newest and active versions differ.Likely an incorrect or invalid review comment.
application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/PetriNetTest.groovy (1)
139-139: LGTM: Improved error message clarity.The updated exception message is more specific and actionable, including the process identifier and version number. This aligns with better error reporting practices.
application-engine/src/test/resources/petriNets/process_version_4_0_0.xml (1)
1-9: LGTM: Test resource completes version coverage.This test resource follows the established pattern of other process version test files and fills the gap for version 4.0.0 in the test suite.
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java (1)
68-72: Good defensive null check; consistent with DashboardItemServiceImpl.The defensive null check and error handling follow the same pattern as DashboardItemServiceImpl, which is good for consistency. The same verification regarding "active" vs "newest" version semantics applies here.
Ensure that the "active" version retrieval is appropriate for dashboard management case creation, consistent with the broader architectural decision reflected in DashboardItemServiceImpl and other services.
application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (1)
36-43: LGTM: Well-documented repository method.The new repository method follows Spring Data MongoDB naming conventions correctly and will be automatically implemented. The Javadoc is clear and properly formatted.
application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy (2)
131-131: LGTM: Test updated for active version semantics.The test correctly uses
getActiveVersionByIdentifierinstead ofgetNewestVersionByIdentifier, aligning with the broader architectural changes in the codebase.
149-149: LGTM: Consistent version retrieval in test.This change is consistent with line 131 and the broader test updates across the repository.
application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy (1)
48-48: LGTM: Test updated for active version semantics.This change aligns with the consistent pattern across all test files, replacing
getNewestVersionByIdentifierwithgetActiveVersionByIdentifier.application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/FieldActionsRunner.groovy (1)
80-86: Cache method rename verified.The method has been successfully renamed from
getNamespaceFunctionCache()togetGlobalFunctionsCache(). The new method is defined in both the interface and implementation, and the usage in FieldActionsRunner.groovy is correct. No references to the old method name remain in the codebase.application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java (1)
66-70: Good defensive null check; pattern aligns with codebase standards.The null check and clear exception message are good defensive programming. The use of
getActiveVersionByIdentifieris consistent with the established pattern throughout the codebase—including similar services likeDashboardManagementServiceImpl—for retrieving process versions when creating cases. No competing "newest version" method exists; the active version retrieval is the standard, intended approach used consistently across workflow operations.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (1)
240-240: LGTM! Consistent migration to active version lookups.The switch from
getNewestVersionByIdentifiertogetActiveVersionByIdentifieraligns with the broader refactoring across the codebase. Both call sites have appropriate null checks and error messages.Also applies to: 264-264
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)
2447-2453: LGTM! Proper null guard and version lookup.The migration to
getActiveVersionByIdentifierwith a null check and descriptive exception message is consistent with similar changes inRoleActionDelegate.groovyand across the codebase.application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy (1)
54-57: LGTM! Consistent active version lookup with proper null guards.The migration to
getActiveVersionByIdentifierwith null checks and formatted error messages is consistent with the broader refactoring pattern across the codebase.Also applies to: 81-84
application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy (1)
70-77: LGTM! Test updated to reflect active version semantics.The test correctly uses
getPetriNetActive()andgetActiveVersionByIdentifier()to align with the refactored cache configuration and PetriNet service API.nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java (1)
76-79: LGTM! Well-structured active version flag implementation.The
versionActivefield with@Indexedannotation, proper initialization in constructors, and helper methods (makeActive(),makeInactive()) provide a solid foundation for the active/latest version distinction introduced in this PR.Also applies to: 166-166, 177-177, 441-447
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java (1)
24-24: LGTM! Method renaming aligns with global cache terminology.The renaming from
getNamespaceFunctionCache()togetGlobalFunctionsCache()andclearNamespaceFunctionCache()toclearGlobalFunctionCache()is consistent with the broader cache refactoring (e.g.,CacheMapKeys.FUNCTIONS,GenericMapCache) introduced in this PR. The newcacheAllPetriNetFunctions()method provides a clear entry point for bulk cache population.Also applies to: 30-33
application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy (2)
303-305: LGTM! Helper method standardizes PetriNet import calls.The
importProcesshelper method centralizes the import logic and improves test readability. It's consistently used across existing and new tests.
150-212: LGTM! Comprehensive tests for active version behavior.The new tests (
testVersionsOnImportandtestVersionActiveOnDelete) thoroughly validate:
- Version activation on import
- Version switching and state propagation to Elasticsearch
- Error handling for invalid version imports
- Version activation on delete (oldest version becomes active)
These tests provide solid coverage for the active version feature introduced in this PR.
Also applies to: 214-241
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2)
29-52: LGTM! Cache naming changes align with the new active/latest versioning semantics.The renaming from
petriNetNewesttopetriNetActiveand the addition ofpetriNetLatestandglobalFunctionscache names are consistent with the PR's objective to support distinct active vs. latest version caching.
66-71: Cache set construction is correct.The
getAllCaches()method properly includes all configured cache names in aLinkedHashSetto maintain order and avoid duplicates.application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
34-64: Per-key locking for compute-if-absent is well-implemented.The double-checked locking pattern with per-key locks minimizes contention while ensuring thread-safe lazy loading. The
finallyblock correctly cleans up the lock entry.One minor note: the unchecked cast at lines 39, 47, and 57 could benefit from
@SuppressWarnings("unchecked")on the method to suppress compiler warnings.🔎 Optional: suppress unchecked warning
@Override + @SuppressWarnings("unchecked") public <T> T get(Object key, Callable<T> loader) {application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (2)
27-30: Good use of@RequiredArgsConstructorand migration toCachingConfigurer.The switch from
CachingConfigurerSupport(deprecated) to implementingCachingConfigurerdirectly is the correct modern approach.
42-58: Thread-safe bounded maps are correctly configured.Using
Collections.synchronizedMap(new MaxSizeHashMap<>(...))addresses the thread-safety concern raised in previous reviews. The cache sizes are properly sourced fromfieldRunnerProperties.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
114-134: Good batched processing with error handling incacheAllPetriNetFunctions.The pagination approach prevents loading all PetriNets into memory at once, and the try-catch ensures one failing net doesn't abort the entire operation. The warning log provides good observability.
193-199: Well-implementedgetRequiredCachehelper.This centralized null-check with a clear error message addresses previous review feedback and prevents NPEs throughout the service.
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (6)
8-11: Good: Explicit imports instead of wildcard.This addresses the previous review comment about avoiding
*imports for consistency and preventing type shadowing.
140-154: Cache eviction correctly updated for new active/latest semantics.Both
evictAllCaches()andevictCache()now properly clear/evict frompetriNetActiveandpetriNetLatestcaches, consistent with the renamed cache configuration.
181-252: Comprehensive import flow with proper version management.The
@Transactionalannotation ensures atomicity across the import operation. The version handling logic correctly:
- Prevents duplicate version imports
- Auto-increments version when not specified
- Rejects lower versions than existing latest
- Maintains single active version invariant by inactivating previous
One consideration: the
inactivatedProcessis saved beforenewProcess(lines 243-246). IfdoSaveInternal(newProcess)fails afterdoSaveInternal(inactivatedProcess)succeeds, the transaction rollback should revert both—verify that the MongoDB transaction configuration supports this.
254-271: Good refactoring:doSaveInternalconsolidates save logic.Extracting the common save operations (initialize arcs, evict cache, persist, index) into a protected method improves maintainability and ensures consistency.
310-331: New caching methods properly guard against null identifiers.Both
getActiveVersionByIdentifierandgetLatestVersionByIdentifierinclude null checks and use appropriate@Cacheableannotations withunless = "#result == null"to avoid caching null results.The
getLatestVersionByIdentifierimplementation correctly queries with descending sort on version components to find the highest version.
596-604: Correct fallback: Activates latest version when active version is deleted.This maintains the invariant that there should always be an active version (if any versions exist) by promoting the latest version when the currently active one is deleted.
...ation-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
Outdated
Show resolved
Hide resolved
...a/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
Show resolved
Hide resolved
...on-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java
Outdated
Show resolved
Hide resolved
| petriNets = petriNetService.getAll(Pageable.unpaged()).getContent(); | ||
| } else { | ||
| petriNets = processes.stream().map(process -> petriNetService.getNewestVersionByIdentifier(process)).collect(Collectors.toList()); | ||
| petriNets = processes.stream().map(process -> petriNetService.getActiveVersionByIdentifier(process)).collect(Collectors.toList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Potential NullPointerException from null PetriNet values.
The getActiveVersionByIdentifier call within the stream can return null if a process identifier has no active version. These null values will be collected into the petriNets list, and the subsequent isEmpty() check at line 285 won't catch them. When the code iterates over petriNets at line 302, calling net.getImmediateFields() will throw a NullPointerException.
This could be the source of the test errors reported by Retoocs.
🔎 Apply this diff to filter out null values:
} else {
- petriNets = processes.stream().map(process -> petriNetService.getActiveVersionByIdentifier(process)).collect(Collectors.toList());
+ petriNets = processes.stream()
+ .map(process -> petriNetService.getActiveVersionByIdentifier(process))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| petriNets = processes.stream().map(process -> petriNetService.getActiveVersionByIdentifier(process)).collect(Collectors.toList()); | |
| petriNets = processes.stream() | |
| .map(process -> petriNetService.getActiveVersionByIdentifier(process)) | |
| .filter(Objects::nonNull) | |
| .collect(Collectors.toList()); |
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
around line 283, the stream maps processes to petriNets using
petriNetService.getActiveVersionByIdentifier which can return null, causing
NullPointerExceptions later when iterating and calling net.getImmediateFields();
filter out null results after the map (e.g., add a filter(Objects::nonNull) or
equivalent) before collecting into petriNets, or alternatively replace nulls
with a safe placeholder and handle them explicitly; ensure subsequent checks
consider an empty list after filtering so iteration over petriNets never
encounters null entries.
| PetriNet filterNet = petriNetService.getNewestVersionByIdentifier(FILTER_NET_IDENTIFIER); | ||
| PetriNet filterNet = petriNetService.getActiveVersionByIdentifier(FILTER_NET_IDENTIFIER); | ||
| if (filterNet == null) { | ||
| throw new IllegalStateException("No filter process found or active"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider clarifying the error message.
The message "No filter process found or active" could be more explicit about whether the process doesn't exist or simply isn't active.
🔎 Apply this diff to clarify:
- throw new IllegalStateException("No filter process found or active");
+ throw new IllegalStateException("No active filter process with identifier '" + FILTER_NET_IDENTIFIER + "' found");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| throw new IllegalStateException("No filter process found or active"); | |
| throw new IllegalStateException("No active filter process with identifier '" + FILTER_NET_IDENTIFIER + "' found"); |
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java
around line 271, improve the thrown exception message so it clearly
distinguishes between a missing filter process and an inactive one; change the
message to state "Filter process not found or not active" more explicitly and,
if available in scope, append identifying details (e.g., filter/process id or
name) and the state (missing vs. inactive) so the exception reads like "Filter
process '[id/name]' not found" or "Filter process '[id/name]' found but not
active" depending on the condition.
| if (net == null) { | ||
| throw new IllegalArgumentException("Petri net with identifier [" + identifier + "] does not exist."); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider clarifying the error message.
The error message states "does not exist" but with the change to active-version lookup, the issue could be that the PetriNet exists but is not active. Consider making the error message more specific.
🔎 Apply this diff to clarify the error message:
- throw new IllegalArgumentException("Petri net with identifier [" + identifier + "] does not exist.");
+ throw new IllegalArgumentException("Active Petri net with identifier [" + identifier + "] not found.");Also applies to: 303-305, 312-314
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java
around lines 289-291 (also update similar checks at 303-305 and 312-314), the
IllegalArgumentException message "Petri net with identifier [...] does not
exist." is ambiguous after switching to active-version lookup; change the
message to distinguish between "does not exist" and "exists but no active
version found." Update each thrown exception to indicate that either the Petri
net was not found or that an active version is not available (e.g., "Petri net
with identifier [X] was found but no active version exists" or "Petri net with
identifier [X] does not exist or has no active version") so the error clearly
reflects the active-version lookup outcome.
| /** | ||
| * Compares this version to the other version | ||
| * | ||
| * @param other other version to be compared with | ||
| * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other</0> | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix malformed HTML tag in JavaDoc.
Line 59 contains </0> which is invalid HTML. Remove the closing tag.
🔎 Apply this diff to fix the JavaDoc:
/**
* Compares this version to the other version
*
* @param other other version to be compared with
- * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other</0>
+ * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other
*/📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Compares this version to the other version | |
| * | |
| * @param other other version to be compared with | |
| * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other</0> | |
| */ | |
| /** | |
| * Compares this version to the other version | |
| * | |
| * @param other other version to be compared with | |
| * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other | |
| */ |
🤖 Prompt for AI Agents
In
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java
around lines 55 to 60, the JavaDoc return description contains a malformed HTML
closing tag `</0>` on line 59; remove that invalid tag and replace it with the
intended plain text punctuation (e.g., a period) so the @return line reads
correctly: "0 if the versions equal, <0 if this is lower than other, >0 if this
is higher than other."
Introduce `FunctionValidator` to centralize and streamline deprecated attribute handling. Enhance PetriNet service with `getAllActive` to retrieve only active PetriNets, and update relevant logic and documentation for consistency. Improve clarity in cache-related configurations and rename caching terms for better alignment with global functions.
- refactor version initialization and validation logic on process import - partly enable and update PetriNetServiceTest
- revert merge with rev10
- rename keyword active to default
- use ResourceNotFoundException - add paged result in PetriNetRepository.findByIdentifierAndDefaultVersion
- fix outcome consistency in PetriNetService
# Conflicts: # application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java # application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
- defined more specific documentation on globalFunctions property - added JavaDocs to FunctionValidator.checkDeprecatedAttributes - added functionCachingPageSize to configuration properties
Introduce a guide covering Petriflow functions, their scopes, usage, and examples in Groovy Closure style. Clarifies function declaration, behavior, method overloading, and runtime compilation details.
Corrected spelling errors and clarified some sentences for better readability. Added missing newline formatting to improve document structure.
- fix javadoc
Replaced unsynchronized `MaxSizeHashMap` with `Collections.synchronizedMap` to ensure thread-safe access to action, global function, and function caches. This prevents potential concurrency issues in a multi-threaded environment.
# Conflicts: # application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java # application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Replaced the method call for retrieving the active version of a Petri net with a call to fetch the default version. This ensures consistency in caching behavior by aligning it with the default Petri net configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (2)
301-307: Same breaking change: Verify default version availability.Line 302 also switches to
getDefaultVersionByIdentifier. The null check at line 303 is present, but ensure all process identifiers have a default version designated before deployment.
310-316: Same breaking change: Verify default version availability.Line 311 also switches to
getDefaultVersionByIdentifier. The null check at line 313 is present, but ensure all process identifiers have a default version designated before deployment.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java (1)
267-286: Add null filter to stream to prevent NullPointerException at line 302.Line 283 calls
getDefaultVersionByIdentifierwithout null handling. Since this method can return null when no default version exists (see PetriNetService line 340-350), the list may contain null values. At line 302,net.getImmediateFields()will throw NullPointerException ifnetis null.Additionally, this change switches from retrieving the newest version to the default version, which alters search behavior. Default and newest versions may have different fields, causing search results to differ.
Apply the suggested filter:
Diff
-petriNets = processes.stream().map(process -> petriNetService.getDefaultVersionByIdentifier(process)).collect(Collectors.toList()); +petriNets = processes.stream() + .map(process -> petriNetService.getDefaultVersionByIdentifier(process)) + .filter(Objects::nonNull) + .collect(Collectors.toList());application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (1)
263-268: Missing null check - potential NullPointerException.Unlike the first call site (line 240) and the pattern established in
DashboardItemServiceImplandDashboardManagementServiceImpl, this call togetDefaultVersionByIdentifier("preference_filter_item")directly dereferences the result without a null check. If thepreference_filter_itemprocess is not deployed, this will throw an NPE instead of a meaningful error.Proposed fix: Add null check with error handling
//Creating new Case of preference_filter_item net and setting its data... + PetriNet preferenceFilterNet = petriNetService.getDefaultVersionByIdentifier("preference_filter_item"); + if (preferenceFilterNet == null) { + resultMessage.append("- preference_filter_item process not found! Menu entry was skipped.\n"); + return ""; + } Case menuItemCase = workflowService.createCase( - petriNetService.getDefaultVersionByIdentifier("preference_filter_item").getStringId(), + preferenceFilterNet.getStringId(), item.getEntryName() + "_" + menuIdentifier, "", ActorTransformer.toLoggedUser(userService.getSystem()) ).getCase();
🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java:
- Line 4: Replace the wildcard import
"com.netgrif.application.engine.objects.petrinet.domain.*" with explicit imports
for each concrete class referenced in IPetriNetService; update the import list
to only include the exact types used by the interface (for example PetriNet,
PetriNetDraft, PetriNetVersion, Place, Transition, Arc — or whatever specific
domain classes the file actually references) so the file no longer uses a star
import and only imports the concrete symbols referenced by the interface.
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:
- Around line 246-280: The conditional branches in checkAndHandleProcessVersion
are dense and need brief inline comments to explain each scenario; add short
comments near the blocks that handle: (a) initial import when
existingLatestProcess==null and newProcess.getVersion()==null (initialize new
Version), (b) version auto-increment when newProcess.getVersion()==null (clone
and increment from existingLatestProcess), (c) branch where a provided
newProcess.getVersion() is lower than existingLatestProcess.getVersion() (set
makeNonDefaultCurrentVersion=false), and (d) the two paths that demote the
current default (when existingLatestProcess.isDefaultVersion() vs when falling
back to self.getDefaultVersionByIdentifier) and finally the
newProcess.makeDefault() call; place comments adjacent to variables/conditions
like newProcess, existingLatestProcess, makeNonDefaultCurrentVersion, and
processToMakeNonDefault so future readers can quickly map each conditional to
the scenario described in the PR comment.
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java:
- Around line 97-112: The getCachedFunctions method can NPE when
functionsCache.get(...).get() is called if the native cache isn't a Map and the
entry is missing; replace the current instanceof Map block and manual
containsKey logic in getCachedFunctions with the Cache.get(key, Callable)
pattern to atomically load and store values (use CachedFunction.build(shell,
function) as the loader), then cast the returned value to CachedFunction and add
it to cachedFunctions; ensure you remove reliance on nativeFunctionsCache and
map.containsKey to avoid the null dereference.
- Around line 82-95: getCompiledAction currently assumes
actionsCache.get(stringId) is non-null and that getNativeCache() is a Map; if
nativeActionsCache isn't a Map the population step is skipped and
actionsCache.get(stringId) may be null causing an NPE. Fix by making
getCompiledAction robust: always check actionsCache.get(stringId) for null
before calling .get(), and if missing compute the Closure via
shell.evaluate("{-> " + action.getDefinition() + "}") and put it into
actionsCache (even when nativeActionsCache is not a Map) so caching still works;
reference getCompiledAction, actionsCache, nativeActionsCache,
CacheMapKeys.ACTIONS and shell.evaluate when applying the change.
In
@application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy:
- Around line 151-217: The test testVersionsOnImport uses three
Thread.sleep(5000) calls which are slow and flaky; replace them with a
polling/await strategy that waits until the expected ElasticPetriNet state is
reached (use elasticPetriNetRepository.findById(petriNetX.stringId) and assert
conditions inside the waiter). Implement or reuse a helper like
waitForCondition/awaitility to poll until the Optional is present and its
isDefaultVersion()/defaultVersion matches the expected boolean (with a
reasonable timeout), and swap each Thread.sleep(5000) with calls to that helper;
reference the testVersionsOnImport method and the
elasticPetriNetRepository.findById checks to locate where to change.
In
@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java:
- Around line 441-447: The public mutators makeDefault() and makeNonDefault() on
PetriNet currently allow callers to mark a version default without coordinating
with other versions; change their visibility to package-private (remove public)
and/or add validation logic inside makeDefault() to delegate to or call a
service (e.g., a VersionManagementService) that enforces the “single default per
identifier” rule, or have makeDefault() clear other defaults for the same
identifier by invoking that service; ensure references to
setDefaultVersion(boolean) remain internal and that all external code paths
route through the service-layer API that enforces the constraint.
- Around line 76-79: Add a DB-level unique compound index to the PetriNet entity
to ensure only one default per identifier: update the PetriNet class (where the
field defaultVersion is declared) to declare a unique compound index on
(identifier, defaultVersion) and prefer a partial/filtered unique index where
defaultVersion is true (so only one document with defaultVersion==true per
identifier is allowed). Use the domain's index annotation mechanism (e.g.,
@CompoundIndex with def = "{'identifier': 1, 'defaultVersion': 1}", unique =
true and a partialFilter/partialExpression for {'defaultVersion': true} if
supported) or add an equivalent DB migration to create the unique partial index
at the database level.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (27)
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovyapplication-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.javaapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovyapplication-engine/src/test/resources/change_caseref_value_action_test.xmlapplication-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xmlnae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.javanae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.javanae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
🧰 Additional context used
🧠 Learnings (15)
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovynae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.javaapplication-engine/src/test/resources/change_caseref_value_action_test.xmlapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.javaapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.javaapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovynae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-12-12T13:40:00.786Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java:98-104
Timestamp: 2025-12-12T13:40:00.786Z
Learning: In application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java, the permission constraint logic in buildQuery() intentionally applies negative actor constraints globally: ((roleConstraint AND NOT negRoleConstraint) OR actorConstraint) AND NOT negActorConstraint. This means users in negativeViewActors are excluded even if they have positive role permissions, which is the intended authorization behavior.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
📚 Learning: 2025-12-19T07:55:13.427Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 400
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java:239-247
Timestamp: 2025-12-19T07:55:13.427Z
Learning: In the netgrif/application-engine project, the `taskService.findOne()` method (from ITaskService) never returns null. When a task is not found, it handles the error through other means (such as throwing an exception) rather than returning null, so null checks after `findOne()` calls are unnecessary.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-10-20T11:46:37.958Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 373
File: application-engine/src/main/java/com/netgrif/application/engine/actions/ActionApiImpl.java:105-107
Timestamp: 2025-10-20T11:46:37.958Z
Learning: In ActionApiImpl, the processIdentifier parameter in searchCases() is intentionally unused because it's required by the ActionApi interface for plugin implementations. This implementation returns cases from all processes without filtering by processIdentifier.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.java
📚 Learning: 2025-12-19T09:06:38.316Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 398
File: application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java:57-58
Timestamp: 2025-12-19T09:06:38.316Z
Learning: In the file `application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java`, the `definition` attribute of the `Action` class (from `com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action`) is never null, so null checks for `action.getDefinition()` are not needed.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/CaseSearchService.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovyapplication-engine/src/test/resources/change_caseref_value_action_test.xmlapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.javaapplication-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovyapplication-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xmlapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-12-12T12:36:06.651Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/resources/petriNets/engine-processes/org_group.xml:36-39
Timestamp: 2025-12-12T12:36:06.651Z
Learning: In the org_group.xml file (application-engine/src/main/resources/petriNets/engine-processes/org_group.xml), variable names in action blocks like userField and user_selection should be kept as-is even when the field type changes from "user" to "actor".
Applied to files:
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovyapplication-engine/src/test/resources/change_caseref_value_action_test.xmlapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.javaapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xmlapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-12T12:40:25.863Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy:18-20
Timestamp: 2025-12-12T12:40:25.863Z
Learning: In Groovy/Java, if a parameter or collection expects ActorFieldValue, you can pass UserFieldValue (a subclass) due to polymorphism. In tests, verify that ActorListFieldValue accepts ActorFieldValue instances and that passing UserFieldValue instances is valid without casts. This pattern applies to similar Groovy files across the repository.
Applied to files:
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovyapplication-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
📚 Learning: 2025-06-23T13:30:13.096Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 318
File: nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java:60-62
Timestamp: 2025-06-23T13:30:13.096Z
Learning: In MenuItemConstants enum in nae-object-library/src/main/java/com/netgrif/application/engine/objects/workflow/domain/menu/MenuItemConstants.java, the field `attributeId` will be renamed to `value` to make it more generic and appropriate for both dataset attribute identifiers and technical constants like PATH_SEPARATOR.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java
📚 Learning: 2025-09-29T10:31:31.469Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java:43-45
Timestamp: 2025-09-29T10:31:31.469Z
Learning: In the Netgrif application engine, when Authentication objects reach controller endpoints, auth.getPrincipal() always returns a LoggedUser instance and is never null, so defensive casting checks are not necessary in controller methods.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.javaapplication-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java
📚 Learning: 2025-12-12T12:37:58.852Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy:4-4
Timestamp: 2025-12-12T12:37:58.852Z
Learning: In tests under application-engine/src/test, GroupService.getDefaultSystemGroup() lazily creates the default system group if it doesn't exist. Tests can rely on this behavior and omit explicit existence/setup checks for the default system group.
Applied to files:
application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy
📚 Learning: 2025-09-05T10:21:54.893Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 350
File: application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy:135-135
Timestamp: 2025-09-05T10:21:54.893Z
Learning: In ExportServiceTest.groovy, writing to src/test/resources is intentional to simulate production behavior where the working tree is mutated during file exports. This mirrors how the system works in production.
Applied to files:
application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovyapplication-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml
📚 Learning: 2025-07-29T17:19:18.300Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.300Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using Lazy setter injection rather than constructor injection. This pattern with Lazy Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.
Applied to files:
application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovyapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
📚 Learning: 2025-09-29T10:31:57.325Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java:513-529
Timestamp: 2025-09-29T10:31:57.325Z
Learning: PetriNet.getStringId() returns a simple ObjectId string representation (_id.toString()), not a composite Netgrif ID format, so new ObjectId(petriNetId) works correctly when petriNetId comes from PetriNet.getStringId().
Applied to files:
application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovyapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
🧬 Code graph analysis (2)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
Slf4j(13-111)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java (1)
ActorTransformer(10-131)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (37)
application-engine/src/test/groovy/com/netgrif/application/engine/export/service/ExportServiceTest.groovy (1)
131-131: The concern about a version mismatch is incorrect. When a PetriNet is created viaimportHelper.createNet(fileName, VersionType.MAJOR), theimportPetriNetmethod automatically callsmakeDefault()on the newly imported process (PetriNetService.java, lines 275-276). This ensures that the newly created test PetriNet with identifier "export_test" is set as the default version and will be retrieved bygetDefaultVersionByIdentifier("export_test")at lines 131 and 149. TheprocessIdretrieved will matchtestNet.stringId, so the task queries will find the expected results. No fix is needed.application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/ImporterTest.groovy (1)
133-133: LGTM - Test updated to use renamed API method.The test correctly uses
getDefaultVersionByIdentifierinstead of the deprecatedgetNewestVersionByIdentifier, aligning with the API refactoring that introduces default/active version semantics.Also applies to: 207-207
application-engine/src/test/groovy/com/netgrif/application/engine/workflow/NewInitTest.groovy (1)
48-48: LGTM - Correct usage of renamed method.The test properly uses
getDefaultVersionByIdentifierto retrieve the PetriNet for case creation, consistent with the API refactoring.application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (1)
36-44: LGTM - Well-defined repository query method.The new
findByIdentifierAndDefaultVersionmethod follows Spring Data naming conventions and properly supports the default version feature with pagination. The Javadoc clearly documents the method's behavior.application-engine/src/main/groovy/com/netgrif/application/engine/startup/ImportHelper.groovy (1)
131-131: LGTM - Import helper updated consistently.Both
upsertNetandimportProcessOncemethods now correctly usegetDefaultVersionByIdentifierto check for existing PetriNets, aligning with the API refactoring.Also applies to: 252-252
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (1)
153-166: Semantic distinction between "default" and "latest" versions is correctly implemented.The refactoring properly replaces
getNewestVersionByIdentifierwith two semantically distinct methods:
getDefaultVersionByIdentifier(line 345): Queries viafindByIdentifierAndDefaultVersion(identifier, true, ...)to retrieve the version marked as the active/designated version by a boolean flag.getLatestVersionByIdentifier(lines 359-360): Queries viafindByIdentifiersorted descending by semantic version (major.minor.patch) to retrieve the most recent version regardless of default status.No remaining references to the old method name exist. Usage patterns across FieldActionsCacheService, WorkflowService, MenuImportExportService, FilterImportExportService, CaseSearchService, and startup runners confirm that cases previously relying on "newest" behavior now correctly use "default" for business logic operations, while "latest" is reserved for internal version management within PetriNetService (e.g., when automatically promoting the latest to default after deletion).
nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java (1)
143-167: Good practice: Explicit initialization of defaultVersion.Initializing
defaultVersionviamakeNonDefault()ensures all new PetriNet instances consistently start in a non-default state, preventing accidental default designation.nae-object-library/src/main/java/com/netgrif/application/engine/objects/elastic/domain/ElasticPetriNet.java (1)
27-27: LGTM: ElasticPetriNet correctly mirrors defaultVersion field.The
defaultVersionfield is properly initialized in the constructor and updated in theupdate()method, maintaining consistency with the source PetriNet object.Also applies to: 39-48, 50-58
nae-object-library/src/main/java/com/netgrif/application/engine/objects/common/ResourceNotFoundExceptionCode.java (1)
9-10: LGTM: New exception code follows established pattern.The
DEFAULT_PROCESS_NOT_FOUNDenum constant is properly added to support error handling for missing default process versions.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java (1)
287-293: Incorrect method name in review comment.The review references
getNewestVersionByIdentifier, which does not exist in the codebase. The actual change is fromgetLatestVersionByIdentifiertogetDefaultVersionByIdentifier.This changes which PetriNet version is used when creating cases by identifier. However, the framework design ensures a default version is always maintained: when new processes are imported, they are automatically marked as default, and when the default is deleted, the latest version is automatically promoted to default. The null check at line 289 is appropriate for robustness despite these guarantees.
application-engine/src/test/resources/petriNets/NAE-1290_Export_actions.xml (1)
220-220: LGTM - API change aligned with default version semantics.The change from
getNewestVersionByIdentifiertogetDefaultVersionByIdentifieris consistent with the PR's objective to standardize on default version lookups. In a test resource context, if the net is missing, the resulting NPE will appropriately fail the test.application-engine/src/main/java/com/netgrif/application/engine/startup/runner/ImpersonationRunner.java (1)
41-46: LGTM - Null handling already in place.The API migration to
getDefaultVersionByIdentifieris correct, and the existing null check on line 43 properly handles the case where no default version exists, proceeding to create the net via the helper.application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultFiltersRunner.java (1)
425-428: LGTM - Graceful null handling preserved.The migration to
getDefaultVersionByIdentifiermaintains the existing graceful degradation pattern - returningOptional.empty()when the filter process doesn't exist, allowing callers to handle the absence appropriately.application-engine/src/test/groovy/com/netgrif/application/engine/impersonation/ImpersonationServiceTest.groovy (1)
314-314: LGTM - Test helper aligned with API change.The API migration in the test helper is consistent with the PR's standardization on default version lookups. Since this is test code (and the test class is disabled pending impersonation rework), a missing net would appropriately fail the test setup.
application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardManagementServiceImpl.java (1)
70-74: LGTM - Proper null handling with explicit exception.The migration to
getDefaultVersionByIdentifierwith an explicit null check andResourceNotFoundExceptionis the correct pattern for production service code. This provides clear error messaging when the dashboard management process is not configured.application-engine/src/main/java/com/netgrif/application/engine/menu/services/DashboardItemServiceImpl.java (1)
68-72: LGTM - Consistent null handling pattern.The implementation mirrors
DashboardManagementServiceImplwith proper null checking and exception handling. This ensures consistent behavior across dashboard-related services when the required process is not found.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/MenuImportExportService.java (1)
240-258: LGTM - Null handling with descriptive messaging.The API migration properly leverages the existing null check, appending a meaningful message to the result when the referenced net is not found.
application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/delegate/RoleActionDelegate.groovy (2)
53-59: LGTM: Default version lookup with proper error handling.The change from
getNewestVersionByIdentifiertogetDefaultVersionByIdentifieraligns with the project-wide version semantics refactoring. The null check with a clear exception message ensures robust error handling when a process cannot be found.
80-86: LGTM: Consistent error handling pattern.The implementation mirrors the null-check pattern in
assignRole(lines 53-59), ensuring consistent behavior across role manipulation operations.application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy (1)
2446-2453: LGTM: Proper validation in role collection.The change to
getDefaultVersionByIdentifierwith null validation is appropriate. The exception message clearly indicates the context ("when collecting roles"), which aids debugging. Note that this exception will propagate out of thecollectEntriesclosure, which is the intended behavior.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FilterImportExportService.java (1)
271-274: LGTM: Appropriate use of domain-specific exception.The change to
getDefaultVersionByIdentifierwith null validation is correct. UsingResourceNotFoundExceptionwith a specific exception code (DEFAULT_PROCESS_NOT_FOUND) is appropriate for the service layer and provides better error categorization than theIllegalArgumentExceptionused in the Groovy action delegates.application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy (1)
69-78: LGTM: Test correctly updated for default version caching.The test properly validates the caching behavior for
getDefaultVersionByIdentifier. All three cache assertions consistently referencegetPetriNetDefault(), confirming that:
- The cache key has been updated from "newest" to "default"
- Lazy caching behavior is preserved
- The cached value matches the retrieved value
application-engine/src/test/resources/change_caseref_value_action_test.xml (2)
32-32: LGTM: Test resource updated to use default version API.The change from
getNewestVersionByIdentifiertogetDefaultVersionByIdentifiercorrectly aligns this test resource with the refactored API.
46-46: LGTM: Consistent API usage in test scenario.This test scenario for handling invalid nets now uses the default version lookup, consistent with line 32 and the broader API refactoring.
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2)
29-52: LGTM! Cache properties correctly align with the new default/latest version semantics.The new
petriNetDefault,petriNetLatest, andglobalFunctionscache names are well-documented and consistent with the broader caching refactor. Based on learnings, theglobalFunctionsproperty name is correct (renamed from namespace-scoped to global-scoped function caching).
66-71: LGTM!getAllCaches()correctly includes all configured cache names.The method properly returns all cache names including the new
petriNetDefault,petriNetLatest, andglobalFunctionscaches.application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy (4)
99-101: Verify whyprocessImportAndDeletetest is disabled.This test was working before and is now marked with
@Disabled. The PR comment mentions "There are test errors." If this test is disabled to work around failures, consider fixing the underlying issues rather than disabling the test.
219-246: LGTM! Good test coverage for default version reassignment on delete.The test correctly verifies that when the default version is deleted, the latest remaining version becomes the new default. This aligns with the logic in
PetriNetService.deletePetriNet().
248-250: Verify whyprocessSearchtest is disabled.Similar to
processImportAndDelete, this test is newly disabled. Confirm this is intentional and not masking test failures from the changes in this PR.
309-311: LGTM! Good refactor extracting the import helper method.The helper method improves readability and reduces code duplication across tests.
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (2)
114-134: LGTM! Good batch caching with error handling.The
cacheAllPetriNetFunctionsmethod correctly paginates through all PetriNets and handles individual failures gracefully without stopping the entire batch. The error logging is appropriate.
193-199: LGTM! Good defensive helper method.The
getRequiredCachemethod provides a clear error message when a cache is not configured, which aids debugging.application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (5)
140-154: LGTM! Cache eviction updated correctly for new cache names.The
evictAllCachesandevictCachemethods properly handle the newpetriNetDefaultandpetriNetLatestcaches.
180-224: LGTM! Import flow correctly handles version transitions with@Transactional.The
@Transactionalannotation ensures atomicity when saving both the process being made non-default and the new process. The order of operations (saveprocessToMakeNonDefaultbeforenewProcess) correctly ensures there's never a state with two default versions.
314-324: Good defensive null check added.The null guards for
identifierandversionprevent cache key issues and NPEs. Theunless = "#result == null"in@Cacheablecorrectly avoids caching null results.
338-365: LGTM! New default/latest version retrieval methods are well-implemented.Both methods:
- Guard against null identifiers
- Use appropriate
@Cacheablewithunless = "#result == null"getDefaultVersionByIdentifierqueries bydefaultVersion=truegetLatestVersionByIdentifiersorts by version components descendingBased on learnings, this aligns with the design where all existing nets have a version assigned.
630-638: LGTM! Default version reassignment on delete is correct.When the deleted version was the default, the code correctly:
- Fetches the latest remaining version (after cache eviction)
- Makes it the new default
- Persists the change
This aligns with the test
testVersionActiveOnDelete.
| private PetriNet checkAndHandleProcessVersion(PetriNet newProcess, VersionType releaseType) { | ||
| PetriNet processToMakeNonDefault = null; | ||
|
|
||
| if (newProcess.getVersion() != null && self.getPetriNet(newProcess.getIdentifier(), newProcess.getVersion()) != null) { | ||
| throw new IllegalArgumentException("A process [%s] with such version [%s] already exists" | ||
| .formatted(newProcess.getIdentifier(), newProcess.getVersion())); | ||
| } | ||
| PetriNet existingLatestProcess = self.getLatestVersionByIdentifier(newProcess.getIdentifier()); | ||
| boolean makeNonDefaultCurrentVersion = true; | ||
| if (existingLatestProcess == null && newProcess.getVersion() == null) { | ||
| newProcess.setVersion(new Version()); | ||
| } else { | ||
| if (newProcess.getVersion() == null) { | ||
| newProcess.setVersion(existingLatestProcess.getVersion().clone()); | ||
| newProcess.incrementVersion(releaseType); | ||
| } else if (existingLatestProcess != null && newProcess.getVersion().isLowerThan(existingLatestProcess.getVersion())) { | ||
| makeNonDefaultCurrentVersion = false; | ||
| } | ||
| if (makeNonDefaultCurrentVersion && existingLatestProcess != null && existingLatestProcess.isDefaultVersion()) { | ||
| existingLatestProcess.makeNonDefault(); | ||
| processToMakeNonDefault = existingLatestProcess; | ||
| } else if (makeNonDefaultCurrentVersion) { | ||
| PetriNet existingActiveProcess = self.getDefaultVersionByIdentifier(newProcess.getIdentifier()); | ||
| if (existingActiveProcess != null) { | ||
| existingActiveProcess.makeNonDefault(); | ||
| processToMakeNonDefault = existingActiveProcess; | ||
| } | ||
| } | ||
| } | ||
| if (makeNonDefaultCurrentVersion) { | ||
| newProcess.makeDefault(); | ||
| } | ||
|
|
||
| return processToMakeNonDefault; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Complex version handling logic - consider adding inline comments.
The checkAndHandleProcessVersion method handles multiple scenarios correctly, but the conditional logic (especially lines 261-273) is dense. The Javadoc is helpful, but inline comments within the method body would improve maintainability.
Key scenarios handled:
- First import (no existing version) → initialize to 1.0.0
- Version not specified → increment from latest
- Version specified but lower than latest → don't make default
- Latest is currently default → make it non-default
- Latest is not default → find and update current default
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
around lines 246 - 280, The conditional branches in checkAndHandleProcessVersion
are dense and need brief inline comments to explain each scenario; add short
comments near the blocks that handle: (a) initial import when
existingLatestProcess==null and newProcess.getVersion()==null (initialize new
Version), (b) version auto-increment when newProcess.getVersion()==null (clone
and increment from existingLatestProcess), (c) branch where a provided
newProcess.getVersion() is lower than existingLatestProcess.getVersion() (set
makeNonDefaultCurrentVersion=false), and (d) the two paths that demote the
current default (when existingLatestProcess.isDefaultVersion() vs when falling
back to self.getDefaultVersionByIdentifier) and finally the
newProcess.makeDefault() call; place comments adjacent to variables/conditions
like newProcess, existingLatestProcess, makeNonDefaultCurrentVersion, and
processToMakeNonDefault so future readers can quickly map each conditional to
the scenario described in the PR comment.
| void testVersionsOnImport() { | ||
| ImportPetriNetEventOutcome outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("2"), superCreator.loggedSuper) | ||
| PetriNet petriNetV2 = outcome.getNet() | ||
| assert petriNetV2 != null | ||
| assert petriNetV2.defaultVersion | ||
| Version version = new Version() | ||
| version.setMajor(2) | ||
| assert petriNetV2.getVersion() == version | ||
| Thread.sleep(5000) | ||
| Optional<ElasticPetriNet> elasticPetriNetV2Optional = elasticPetriNetRepository.findById(petriNetV2.stringId) | ||
| assert elasticPetriNetV2Optional.isPresent() | ||
| assert elasticPetriNetV2Optional.get().isDefaultVersion() | ||
|
|
||
| outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("4"), superCreator.loggedSuper) | ||
| PetriNet petriNetV4 = outcome.getNet() | ||
| assert petriNetV4 != null | ||
| assert !petriNetService.get(petriNetV2.getObjectId()).isDefaultVersion() | ||
| assert petriNetV4.isDefaultVersion() | ||
| version = new Version() | ||
| version.setMajor(4) | ||
| assert petriNetV4.getVersion() == version | ||
| Thread.sleep(5000) | ||
| elasticPetriNetV2Optional = elasticPetriNetRepository.findById(petriNetV2.stringId) | ||
| assert !elasticPetriNetV2Optional.get().isDefaultVersion() | ||
| Optional<ElasticPetriNet> elasticPetriNetV4Optional = elasticPetriNetRepository.findById(petriNetV4.stringId) | ||
| assert elasticPetriNetV4Optional.isPresent() | ||
| assert elasticPetriNetV4Optional.get().isDefaultVersion() | ||
|
|
||
| outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("1"), superCreator.loggedSuper) | ||
| PetriNet petriNetV1 = outcome.getNet() | ||
| assert petriNetV1 != null | ||
| assert petriNetService.get(petriNetV4.getObjectId()).isDefaultVersion() | ||
| assert !petriNetV1.isDefaultVersion() | ||
| version = new Version() | ||
| version.setMajor(1) | ||
| assert petriNetV1.getVersion() == version | ||
|
|
||
| assertThrows(IllegalArgumentException.class, { | ||
| // cannot import already existing version | ||
| importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("2"), superCreator.loggedSuper) | ||
| }) | ||
|
|
||
| petriNetV2.makeDefault() | ||
| petriNetV2 = petriNetService.save(petriNetV2).get() | ||
| assert petriNetV2.defaultVersion | ||
| petriNetV4.makeNonDefault() | ||
| petriNetV4 = petriNetService.save(petriNetV4).get() | ||
| assert !petriNetV4.defaultVersion | ||
|
|
||
| outcome = importProcess(VERSION_PROCESS_FILE_FORMAT.formatted("5"), superCreator.loggedSuper) | ||
| PetriNet petriNetV5 = outcome.getNet() | ||
| assert petriNetV5 != null | ||
| assert !petriNetService.get(petriNetV2.getObjectId()).defaultVersion | ||
| assert !petriNetService.get(petriNetV4.getObjectId()).defaultVersion | ||
| assert petriNetV5.defaultVersion | ||
| version = new Version() | ||
| version.setMajor(5) | ||
| assert petriNetV5.getVersion() == version | ||
| Thread.sleep(5000) | ||
| elasticPetriNetV2Optional = elasticPetriNetRepository.findById(petriNetV2.stringId) | ||
| assert !elasticPetriNetV2Optional.get().defaultVersion | ||
| elasticPetriNetV4Optional = elasticPetriNetRepository.findById(petriNetV4.stringId) | ||
| assert !elasticPetriNetV4Optional.get().defaultVersion | ||
| Optional<ElasticPetriNet> elasticPetriNetV5Optional = elasticPetriNetRepository.findById(petriNetV5.stringId) | ||
| assert elasticPetriNetV5Optional.isPresent() | ||
| assert elasticPetriNetV5Optional.get().defaultVersion | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Good coverage for version import semantics, but Thread.sleep(5000) is fragile.
The test comprehensively covers the default version behavior during imports. However, the multiple Thread.sleep(5000) calls (lines 159, 172, 209) make this test slow (~15 seconds of sleep) and potentially flaky if Elasticsearch indexing takes longer than expected.
Consider using an await/polling pattern or a test utility that waits for the expected Elasticsearch state.
🤖 Prompt for AI Agents
In
@application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
around lines 151 - 217, The test testVersionsOnImport uses three
Thread.sleep(5000) calls which are slow and flaky; replace them with a
polling/await strategy that waits until the expected ElasticPetriNet state is
reached (use elasticPetriNetRepository.findById(petriNetX.stringId) and assert
conditions inside the waiter). Implement or reuse a helper like
waitForCondition/awaitility to poll until the Optional is present and its
isDefaultVersion()/defaultVersion matches the expected boolean (with a
reasonable timeout), and swap each Thread.sleep(5000) with calls to that helper;
reference the testVersionsOnImport method and the
elasticPetriNetRepository.findById checks to locate where to change.
| @Getter | ||
| @Setter | ||
| @Indexed | ||
| private boolean defaultVersion; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for defaultVersion usage across the codebase
echo "=== Searching for defaultVersion usage ==="
rg -n 'defaultVersion' --type java -B2 -A2
echo -e "\n=== Searching for version management methods ==="
rg -n 'setDefault|makeDefault|getDefault|Default.*Version' --type java | head -50
echo -e "\n=== Searching for version-related services/repositories ==="
fd -e java -path '*service*' -o -path '*repository*' | head -20Repository: netgrif/application-engine
Length of output: 16339
🏁 Script executed:
#!/bin/bash
echo "=== Searching for getDefaultVersionByIdentifier implementation ==="
rg -n 'getDefaultVersionByIdentifier' --type java -B5 -A10
echo -e "\n=== Searching for PetriNetService class ==="
fd -name 'PetriNetService.java' | head -5
echo -e "\n=== Searching for methods that update defaultVersion ==="
rg -n 'setDefaultVersion|\.defaultVersion\s*=' --type java -B3 -A3Repository: netgrif/application-engine
Length of output: 46164
🏁 Script executed:
#!/bin/bash
echo "=== Searching for MongoDB unique index configuration ==="
rg -n '@Indexed|@Document|unique' --type java application-engine/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
echo -e "\n=== Checking PetriNetService import context ==="
fd -name 'PetriNetService.java' -exec head -30 {} \;
echo -e "\n=== Searching for validation or constraints on defaultVersion ==="
rg -n 'defaultVersion.*unique|unique.*defaultVersion|Constraint|@[A-Z].*unique' --type javaRepository: netgrif/application-engine
Length of output: 9419
🏁 Script executed:
#!/bin/bash
echo "=== Finding PetriNet.java file ==="
find . -name 'PetriNet.java' -type f | head -10
echo -e "\n=== Search for PetriNet class with @Document annotation ==="
rg -l 'class PetriNet' --type java | head -5
echo -e "\n=== Looking for transaction management in version updates ==="
rg '@Transactional|makeDefault|makeNonDefault' --type java -B2 -A5 | head -100Repository: netgrif/application-engine
Length of output: 15148
Consider adding a database-level unique constraint on (identifier, defaultVersion=true) to prevent concurrent race conditions.
While the service layer enforces single default through @Transactional methods (e.g., importPetriNet ensures the old default is marked non-default before the new one becomes default), there is no database-level constraint preventing multiple versions from being marked as default if updates bypass the service logic or occur concurrently. Adding a unique compound index would provide additional safety without impacting normal operations.
🤖 Prompt for AI Agents
In
@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
around lines 76 - 79, Add a DB-level unique compound index to the PetriNet
entity to ensure only one default per identifier: update the PetriNet class
(where the field defaultVersion is declared) to declare a unique compound index
on (identifier, defaultVersion) and prefer a partial/filtered unique index where
defaultVersion is true (so only one document with defaultVersion==true per
identifier is allowed). Use the domain's index annotation mechanism (e.g.,
@CompoundIndex with def = "{'identifier': 1, 'defaultVersion': 1}", unique =
true and a partialFilter/partialExpression for {'defaultVersion': true} if
supported) or add an equivalent DB migration to create the unique partial index
at the database level.
| public void makeDefault() { | ||
| this.setDefaultVersion(true); | ||
| } | ||
|
|
||
| public void makeNonDefault() { | ||
| this.setDefaultVersion(false); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider restricting access or adding validation to default version methods.
The makeDefault() and makeNonDefault() methods are public but don't coordinate with any service-layer logic to ensure only one version per identifier is marked as default. This could lead to inconsistent state if called directly.
Consider:
- Making these methods package-private and routing calls through a service that enforces the single-default constraint
- Adding validation within these methods to clear other defaults
Based on learnings, PetriNet version management is typically handled by service-layer processes. These domain methods should align with that pattern.
🤖 Prompt for AI Agents
In
@nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/PetriNet.java
around lines 441 - 447, The public mutators makeDefault() and makeNonDefault()
on PetriNet currently allow callers to mark a version default without
coordinating with other versions; change their visibility to package-private
(remove public) and/or add validation logic inside makeDefault() to delegate to
or call a service (e.g., a VersionManagementService) that enforces the “single
default per identifier” rule, or have makeDefault() clear other defaults for the
same identifier by invoking that service; ensure references to
setDefaultVersion(boolean) remain internal and that all external code paths
route through the service-layer API that enforces the constraint.
# Conflicts: # application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1)
20-23: Update stale Javadoc and add backward-compatible binding for renamed property.The Javadoc at lines 20-23 still mentions "namespace cache sizes" despite the property being renamed to
globalFunctionsCacheSize. More importantly, existing deployments usingnetgrif.engine.runner.field-runner.namespace-cache-sizewill fail to bind since there is no alias setter. Add a deprecated alias to maintain backward compatibility for one release cycle:Proposed fix
/** - * Configuration for the field runner, including action, function, and namespace cache sizes. + * Configuration for the field runner, including action and function cache sizes. */ private FieldRunnerProperties fieldRunner = new FieldRunnerProperties(); @@ /** * The size of the cache used for managing global Petri net functions. */ private int globalFunctionsCacheSize = 500; + /** + * @deprecated Use {@link #setGlobalFunctionsCacheSize(int)} instead. + * Kept for backward compatibility with configs using + * {@code netgrif.engine.runner.field-runner.namespace-cache-size}. + */ + @Deprecated + public void setNamespaceCacheSize(int namespaceCacheSize) { + this.globalFunctionsCacheSize = namespaceCacheSize; + }
🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:
- Around line 49-52: Update the Javadoc on the CacheConfigurationProperties
field globalFunctions to remove redundancy; replace the current sentence
"Default cache name for caching global functions of PetriNet global scoped
functions." with a concise phrasing such as "Default cache name for caching
PetriNet global functions." or "Default cache name for caching global-scoped
functions of PetriNets." Keep the field name globalFunctions unchanged.
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java:
- Around line 62-67: Add bean validation to the functionCachingPageSize field by
annotating the field functionCachingPageSize with @Min(1) so values less than 1
are rejected; also add the corresponding import for
javax.validation.constraints.Min (or jakarta.validation.constraints.Min
consistent with the project) so the annotation compiles. Ensure the annotation
is placed directly above the private int functionCachingPageSize = 500;
declaration.
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java:
- Line 4: Replace the wildcard import "import
com.netgrif.application.engine.objects.petrinet.domain.*;" with explicit imports
for every class or interface referenced by IPetriNetService (e.g., PetriNet,
PetriNetVersion, Place, Transition, Arc, Flow, Marking — or the actual types
used in this interface); locate usages of domain types in the IPetriNetService
method signatures and add individual import statements for each to restore
clarity and avoid type ambiguity.
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java:
- Around line 70-74: Fetch the Petri net version into a local variable from
petriNetService.getDefaultVersionByIdentifier(petriNetIdentifier), check if that
value is null and if so throw the same kind of not-found/runtime exception used
elsewhere (include the petriNetIdentifier in the message), otherwise remove the
entry from globalFunctionsCache and call cachePetriNetFunctions with the
non-null value; update reloadCachedFunctions to perform the null-check before
calling cachePetriNetFunctions and mirror the null-safety pattern used in other
services.
In @docs/process/functions.md:
- Line 7: Edit the sentence "Petriflow functions provide opportunity to declare
your own functions without any changes in applications code." and remove the
trailing space at the end of the line so the Markdown lint error is resolved;
ensure no other trailing whitespace exists on that line.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (10)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.javaapplication-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.javaapplication-engine/src/main/java/com/netgrif/application/engine/importer/service/IFunctionValidator.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javadocs/process/functions.md
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-19T09:06:38.316Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 398
File: application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java:57-58
Timestamp: 2025-12-19T09:06:38.316Z
Learning: In the file `application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java`, the `definition` attribute of the `Action` class (from `com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action`) is never null, so null checks for `action.getDefinition()` are not needed.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-19T07:55:13.427Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 400
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java:239-247
Timestamp: 2025-12-19T07:55:13.427Z
Learning: In the netgrif/application-engine project, the `taskService.findOne()` method (from ITaskService) never returns null. When a task is not found, it handles the error through other means (such as throwing an exception) rather than returning null, so null checks after `findOne()` calls are unnecessary.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-09-29T10:31:57.325Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 362
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/ProcessRoleService.java:513-529
Timestamp: 2025-09-29T10:31:57.325Z
Learning: PetriNet.getStringId() returns a simple ObjectId string representation (_id.toString()), not a composite Netgrif ID format, so new ObjectId(petriNetId) works correctly when petriNetId comes from PetriNet.getStringId().
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.javaapplication-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-07-29T17:19:18.300Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 331
File: application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticPetriNetService.java:45-46
Timestamp: 2025-07-29T17:19:18.300Z
Learning: In ElasticPetriNetService class, petriNetService is properly initialized using Lazy setter injection rather than constructor injection. This pattern with Lazy Autowired setter methods is commonly used in Spring to resolve circular dependencies and is a valid alternative to constructor injection.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
📚 Learning: 2025-12-12T12:36:06.651Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 397
File: application-engine/src/main/resources/petriNets/engine-processes/org_group.xml:36-39
Timestamp: 2025-12-12T12:36:06.651Z
Learning: In the org_group.xml file (application-engine/src/main/resources/petriNets/engine-processes/org_group.xml), variable names in action blocks like userField and user_selection should be kept as-is even when the field type changes from "user" to "actor".
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
🧬 Code graph analysis (3)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java (1)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (1)
Slf4j(10-28)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (1)
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java (1)
ActorTransformer(10-131)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (1)
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java (1)
Component(8-29)
🪛 GitHub Actions: Pull Request
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java
[error] 26-26: cannot find symbol: GLOBAL in class com.netgrif.application.engine.objects.importer.model.Scope
🪛 markdownlint-cli2 (0.18.1)
docs/process/functions.md
7-7: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (25)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java (3)
160-166: LGTM! New method is well-documented.The new
getLatestVersionByIdentifiermethod provides clear semantics distinguishing between "default" and "latest" versions, which aligns with the PR objectives.
176-181: LGTM! New method is well-documented.The
getAllDefaultmethod provides a clear way to retrieve all default version PetriNets with pagination support.
153-158: Ensure all implementations and call sites have been updated to usegetDefaultVersionByIdentifier.The method
getNewestVersionByIdentifierhas been renamed togetDefaultVersionByIdentifier, which is a breaking API change. Verify that all implementations and call sites across the codebase have been updated accordingly.application-engine/src/main/java/com/netgrif/application/engine/importer/service/IFunctionValidator.java (1)
1-15: LGTM! Interface is well-designed and documented.The new
IFunctionValidatorinterface is clean and follows good practices:
- Extends the existing
IModelValidatorfor consistency- Javadoc clearly documents the mutation behavior of
checkDeprecatedAttributes- Single responsibility principle is maintained
application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionFactory.java (1)
15-18: LGTM! Proper dependency injection and validation flow.The addition of
IFunctionValidatorvia constructor injection follows best practices, and callingcheckDeprecatedAttributesbefore processing the function ensures deprecated attributes are normalized early in the pipeline.Note: This code depends on
FunctionValidatorbeing fixed (see compilation error in that file).application-engine/src/main/java/com/netgrif/application/engine/importer/service/FunctionValidator.java (1)
23-28: Verify: Scope constant usage in deprecated attribute checkThe code references
Scope.GLOBALat line 26, but this constant cannot be verified in the codebase. TheScopeclass is generated from an external XSD schema (https://petriflow.org/petriflow.schema.xsd) via JAXB, making its available constants inaccessible for direct verification.Confirm what constants are available in the generated
Scopeenum and whetherScope.GLOBALexists. If not, determine the correct constant name to use when replacing the deprecatedNAMESPACEscope. Note thatFunctionScope(used elsewhere in the codebase) does have aGLOBALconstant, but that is a different class thanScope.application-engine/src/main/java/com/netgrif/application/engine/petrinet/domain/repositories/PetriNetRepository.java (2)
36-44: LGTM!The repository method declaration follows Spring Data MongoDB conventions correctly, and the Javadoc clearly describes the filtering behavior.
82-89: LGTM!The repository method correctly uses Spring Data's derived query naming convention for boolean properties. The Javadoc appropriately describes the method's purpose.
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (2)
30-37: LGTM!The split of the single
petriNetNewestcache into two separate caches (petriNetDefaultandpetriNetLatest) clearly supports the new version management semantics introduced in this PR.
67-68: LGTM!The
getAllCaches()method correctly includes all the new cache names (petriNetDefault,petriNetLatest,globalFunctions) and maintains order usingLinkedHashSet.application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (6)
18-20: LGTM!The pagination imports are appropriately added to support the new
cacheAllPetriNetFunctions()method.
34-34: LGTM!The renaming from
namespaceFunctionsCachetoglobalFunctionsCachecorrectly reflects the scope change. Thread-safety is ensured withCollections.synchronizedMap, and cache bounds are enforced withMaxSizeHashMap.Also applies to: 40-42
46-50: LGTM!The lazy setter injection pattern is appropriate for resolving circular dependencies. This pattern is consistently used in the codebase.
58-67: LGTM!The method correctly filters for global-scoped functions and stores them in
globalFunctionsCachekeyed by PetriNet identifier. The cleanup of empty entries is appropriate.
103-123: LGTM with good error handling.The pagination logic is correct:
isEmpty()check on line 108 prevents processing empty pageshasNext()check on line 117 prevents unnecessary page fetches- Per-PetriNet try-catch on lines 110-114 ensures one failing PetriNet doesn't stop the entire batch
The defensive error handling is particularly valuable for preventing cascading failures during bulk cache population.
163-165: LGTM!The method renames from "namespace" to "global" are consistent with the scope refactoring. Cache reinitialization maintains thread-safety with synchronized maps.
Also applies to: 173-175
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java (9)
142-143: LGTM!Cache eviction logic correctly updated to handle the new
petriNetDefaultandpetriNetLatestcaches, replacing the singlepetriNetNewestcache.Also applies to: 150-151
181-224: LGTM!The import flow correctly:
- Validates and handles version semantics via
checkAndHandleProcessVersion- Saves the demoted process before saving the new process (lines 214-217)
- Caches functions after successful save
- Maintains transactional integrity with
@TransactionalThe order of operations ensures database consistency during version transitions.
283-299: LGTM!The refactoring of
save()to delegate todoSaveInternal()enables reuse in the import flow. Key improvements:
- Indexing errors don't fail the save operation (lines 292-296)
- Proper SLF4J placeholder usage in logging (line 295)
protected finalmodifier prevents subclass override while allowing internal use
314-324: LGTM!The added null guards (lines 315-317) prevent potential issues and align with the cache configuration's
unless = "#result == null"condition.
339-365: LGTM!Both methods are well-implemented with:
- Appropriate caching configuration matching the new cache semantics
- Null-safety for identifier parameter
- Efficient pagination (limit 1) for single-result queries
- Correct sorting for latest version (DESC on major, minor, patch)
The return of
nullwhen not found is consistent withgetPetriNet(String, Version).
417-422: LGTM!The
getAllDefault()method follows the established pattern of other pagination methods in this service, including proper arc initialization.
503-503: LGTM!The updated reference retrieval correctly uses
getDefaultVersionByIdentifierwhen no version is specified, aligning with the new default/latest version semantics.
637-645: LGTM!The default version promotion logic correctly handles deletion:
- Only triggers when deleting a default version (line 637)
- Promotes the remaining latest version to default (line 642)
- Null-safe check prevents errors when no other versions exist (line 639)
The order of operations ensures the deleted process is not considered for promotion (deletion at line 634 occurs before fetching latest at line 638).
226-280: Version management logic is correct; the distinction between default and latest versions is intentional.The code correctly handles the case where the default version differs from the latest version:
Dual version queries (lines 253, 268): The system maintains separate "latest" and "default" version concepts.
getLatestVersionByIdentifier()retrieves the highest version number;getDefaultVersionByIdentifier()retrieves the marked-default version.Default demotion logic (lines 264–273) handles two scenarios:
- If latest is default (line 264): demote it
- If latest is NOT default (line 267): query for and demote any other default version
This is necessary because a non-latest version can legitimately remain default. For example: importing v2.0 (default), then v3.0 (becomes default), then v2.5 (stays non-default because 3.0 is latest).
- Deletion recovery (lines 637–642): When the default version is deleted, the system auto-promotes the latest to default, ensuring consistency.
The complex nesting is justified—the code correctly implements support for independent default/latest version management.
| /** | ||
| * The size of pages used for caching functions when processing large sets of data. | ||
| * This property determines the maximum number of functions to include in a single page during caching operations. | ||
| * Default value is 500. | ||
| */ | ||
| private int functionCachingPageSize = 500; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the property class to see if there are validation annotations
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java | head -100Repository: netgrif/application-engine
Length of output: 3681
🏁 Script executed:
# Search for all usages of functionCachingPageSize in the codebase
rg "functionCachingPageSize" -A 3 -B 3Repository: netgrif/application-engine
Length of output: 1171
🏁 Script executed:
# Check if there are any validation annotations on the property
rg "functionCachingPageSize" --type java -A 5 | grep -E "@|functionCachingPageSize"Repository: netgrif/application-engine
Length of output: 244
🏁 Script executed:
# Look for the caching implementation that uses this property
rg "caching|cache" --type java -l | head -20Repository: netgrif/application-engine
Length of output: 2112
🏁 Script executed:
# Find where functionCachingPageSize is actually used
rg "functionCachingPageSize" --type java -B 5 -A 10Repository: netgrif/application-engine
Length of output: 2625
🏁 Script executed:
# Examine the FunctionsCacheRunner to see how this property is used
cat -n application-engine/src/main/java/com/netgrif/application/engine/startup/runner/FunctionsCacheRunner.javaRepository: netgrif/application-engine
Length of output: 1413
🏁 Script executed:
# Check if there are any validation annotations on similar cache properties in the codebase
rg "@Min|@Max|@Positive|@NotNull" --type java -B 2 -A 2 | grep -A 5 -B 5 "CacheSize\|PageSize"Repository: netgrif/application-engine
Length of output: 52
🏁 Script executed:
# Search for any validation in configuration properties
rg "CacheSize\|PageSize" --type java -A 3 -B 3 | grep -E "@|CacheSize|PageSize"Repository: netgrif/application-engine
Length of output: 52
🏁 Script executed:
# Search for where functionCachingPageSize might be injected or accessed
rg "functionCachingPageSize\|getFieldRunner" --type java -B 3 -A 3Repository: netgrif/application-engine
Length of output: 52
🏁 Script executed:
# Look for pageable or pagination logic in the caching services
rg "PageRequest\|Pageable" --type java -l | grep -i cacheRepository: netgrif/application-engine
Length of output: 52
🏁 Script executed:
# Check FieldActionsCacheService for pagination usage
cat -n application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java | head -150Repository: netgrif/application-engine
Length of output: 7378
🏁 Script executed:
# Check if there are any other usages of functionCachingPageSize beyond what we found
rg "getFunctionCachingPageSize" --type java -B 5 -A 5Repository: netgrif/application-engine
Length of output: 1650
🏁 Script executed:
# Look for all setter/getter patterns related to this property
rg "FunctionCachingPageSize" --type java -iRepository: netgrif/application-engine
Length of output: 443
🏁 Script executed:
# Check what other cache size properties exist and if any have validation
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java | head -100Repository: netgrif/application-engine
Length of output: 2804
🏁 Script executed:
# Check FieldActionsCacheProperties to see if there's any validation pattern there
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/FieldActionsCacheProperties.javaRepository: netgrif/application-engine
Length of output: 692
🏁 Script executed:
# Let's verify Spring Data's PageRequest behavior with a quick search
# Check if there are any tests or documentation about PageRequest.of() behavior
fd "PageRequest" --type f | head -5Repository: netgrif/application-engine
Length of output: 312
🏁 Script executed:
# Look for any error handling or validation patterns in the codebase for similar properties
rg "PageRequest\|IllegalArgumentException.*size\|size.*0" --type java -B 3 -A 3 | head -40Repository: netgrif/application-engine
Length of output: 52
🏁 Script executed:
# Check if there are any validation bean imports in RunnerConfigurationProperties
head -20 application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.javaRepository: netgrif/application-engine
Length of output: 679
🏁 Script executed:
# Search for validation annotations used elsewhere in configuration properties
fd "ConfigurationProperties" --type f -x grep -l "@Min\|@Max\|@Positive\|@NotNull" {} \;Repository: netgrif/application-engine
Length of output: 193
🏁 Script executed:
# Check the DataConfigurationProperties file that was found with validation annotations
cat -n application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java | head -100Repository: netgrif/application-engine
Length of output: 4156
🏁 Script executed:
# Search for @Min, @Max, @Positive annotations in the entire configuration properties
find application-engine/src/main/java/com/netgrif/application/engine/configuration/properties -name "*.java" -exec grep -l "@Min\|@Max\|@Positive" {} \;Repository: netgrif/application-engine
Length of output: 191
🏁 Script executed:
# Look at the actual pagination logic more carefully - check the complete cacheAllPetriNetFunctions method
sed -n '103,123p' application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaRepository: netgrif/application-engine
Length of output: 810
🏁 Script executed:
# Check if MaxSizeHashMap that's used for cache size validation has any constraints
rg "MaxSizeHashMap" --type java -B 5 -A 15 | head -60Repository: netgrif/application-engine
Length of output: 8913
Add @Min(1) validation to functionCachingPageSize.
The property lacks explicit validation despite similar cache size properties in the codebase. While Spring Data's PageRequest.of() validates the size internally and rejects values ≤ 0, adding @Min(1) annotation aligns with validation patterns used elsewhere (e.g., DataConfigurationProperties) and provides explicit contract documentation.
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
around lines 62 - 67, Add bean validation to the functionCachingPageSize field
by annotating the field functionCachingPageSize with @Min(1) so values less than
1 are rejected; also add the corresponding import for
javax.validation.constraints.Min (or jakarta.validation.constraints.Min
consistent with the project) so the annotation compiles. Ensure the annotation
is placed directly above the private int functionCachingPageSize = 500;
declaration.
| import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; | ||
| import com.netgrif.application.engine.objects.petrinet.domain.Transition; | ||
| import com.netgrif.application.engine.objects.petrinet.domain.VersionType; | ||
| import com.netgrif.application.engine.objects.petrinet.domain.*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider using explicit imports instead of wildcard.
The change from explicit imports to a wildcard import reduces code clarity and may introduce ambiguity if multiple types with the same name exist in the package.
♻️ Revert to explicit imports
-import com.netgrif.application.engine.objects.petrinet.domain.*;
+import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
+import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch;
+import com.netgrif.application.engine.objects.petrinet.domain.Transition;
+import com.netgrif.application.engine.objects.petrinet.domain.VersionType;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
at line 4, Replace the wildcard import "import
com.netgrif.application.engine.objects.petrinet.domain.*;" with explicit imports
for every class or interface referenced by IPetriNetService (e.g., PetriNet,
PetriNetVersion, Place, Transition, Arc, Flow, Marking — or the actual types
used in this interface); locate usages of domain types in the IPetriNetService
method signatures and add individual import statements for each to restore
clarity and avoid type ambiguity.
|
|
||
| ## Overview | ||
|
|
||
| Petriflow functions provide opportunity to declare your own functions without any changes in applications code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove trailing space.
Markdownlint detected a trailing space at the end of line 7. Please remove it to comply with markdown formatting standards.
🔧 Proposed fix
-Petriflow functions provide opportunity to declare your own functions without any changes in applications code.
+Petriflow functions provide opportunity to declare your own functions without any changes in applications code.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Petriflow functions provide opportunity to declare your own functions without any changes in applications code. | |
| Petriflow functions provide opportunity to declare your own functions without any changes in applications code. |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
7-7: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
🤖 Prompt for AI Agents
In @docs/process/functions.md at line 7, Edit the sentence "Petriflow functions
provide opportunity to declare your own functions without any changes in
applications code." and remove the trailing space at the end of the line so the
Markdown lint error is resolved; ensure no other trailing whitespace exists on
that line.
# Conflicts: # application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
# Conflicts: # application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
Moved `functionCachingPageSize` from `RunnerConfigurationProperties` to `CacheConfigurationProperties` to improve property organization and maintainability. This ensures related cache settings are grouped together for better clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java (1)
49-59: Clarify the distinction betweenfunctionsCacheSizeandglobalFunctionsCacheSize.With the addition of
globalFunctionsCacheSize, there are now two function-related cache properties inFieldRunnerProperties:
functionsCacheSize(line 54) - "for managing field runner functions"globalFunctionsCacheSize(line 59) - "for managing global Petri net functions"The semantic distinction between "field runner functions" and "global Petri net functions" is unclear from the current documentation. Please enhance the Javadocs to clarify:
- What types of functions each cache manages
- When users should configure one vs. the other
- How these caches interact with the broader caching infrastructure
This will help maintainers and users understand the configuration options and set appropriate values.
Based on learnings, this refactoring introduces global-scoped function caching to replace namespace-scoped caching. Consider adding this context to the documentation.
🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:
- Around line 49-52: Update the Javadoc for the CacheConfigurationProperties
field globalFunctions to remove the redundant wording; replace "Default cache
name for caching global functions of PetriNet global scoped functions." with a
simpler sentence such as "Default cache name for caching PetriNet global-scoped
functions." (keep field name globalFunctions unchanged).
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java:
- Around line 57-59: CacheConfiguration.cacheManager() currently registers the
"globalFunctions" cache as a plain ConcurrentMapCache and ignores the
FieldRunnerProperties.globalFunctionsCacheSize; change the registration to
construct the "globalFunctions" cache using GenericMapCache with a supplier that
reads fieldRunnerProperties.getGlobalFunctionsCacheSize(), mirroring how the
"functions" cache is created, so the globalFunctions cache is bounded by the
configured size (update the cacheManager() method to replace the
ConcurrentMapCache instantiation for "globalFunctions" with a GenericMapCache
supplier using fieldRunnerProperties.getGlobalFunctionsCacheSize()).
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java:
- Around line 97-112: getCachedFunctions has an NPE risk similar to
getCompiledAction because it calls functionsCache.get(...).get() without
guaranteeing the entry exists; if nativeFunctionsCache is not a Map the put is
skipped but the subsequent get() still occurs. Fix by ensuring you never call
.get() on a null Cache.Value: when iterating functions, first retrieve
Cache.Value v = functionsCache.get(id) and if v == null create the
CachedFunction via CachedFunction.build(shell, function) and insert it (use
functionsCache.putIfAbsent / put), then re-read v = functionsCache.get(id) and
only then cast and add ((CachedFunction) v.get()); alternatively use a
compute-if-absent pattern so the cached value is always present before calling
.get(). Ensure this logic is applied in getCachedFunctions (and mirror the same
null-safe pattern used in getCompiledAction).
- Around line 71-75: The reloadCachedFunctions method evicts the cache
unconditionally and then calls cachePetriNetFunctions with
petriNetService.getDefaultVersionByIdentifier(petriNetId) which may return null,
leaving the cache empty without notice; update reloadCachedFunctions to first
fetch the default version into a local variable via
petriNetService.getDefaultVersionByIdentifier(petriNetId), check for null, and
if null either skip eviction and log a warning (using the existing logger) or
evict and then log a warning that no default version exists, otherwise proceed
to call cachePetriNetFunctions with the non-null result; reference methods:
reloadCachedFunctions, getRequiredCache(properties.getGlobalFunctions()),
petriNetService.getDefaultVersionByIdentifier(...) and cachePetriNetFunctions.
- Around line 82-95: In getCompiledAction modify logic to avoid calling .get()
on a possibly null Cache.ValueWrapper: after obtaining actionsCache and
nativeActionsCache, compute/put the compiled Closure (using shell.evaluate("{->
" + action.getDefinition() + "}")) when needed as you already do, then fetch
Cache.ValueWrapper wrapper = actionsCache.get(stringId) and check for null
before calling wrapper.get(); if wrapper is null (native cache not a Map or
evicted race), return the freshly compiled Closure (or re-evaluate and put it
into actionsCache) so the method always returns a Closure without risking a
NullPointerException. Ensure you reference getCompiledAction, actionsCache,
Cache.ValueWrapper and shell.evaluate in your changes.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.javaapplication-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.javaapplication-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
📚 Learning: 2025-11-14T10:22:01.634Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 383
File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43
Timestamp: 2025-11-14T10:22:01.634Z
Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
📚 Learning: 2025-07-31T23:40:46.499Z
Learnt from: tuplle
Repo: netgrif/application-engine PR: 334
File: application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java:204-214
Timestamp: 2025-07-31T23:40:46.499Z
Learning: In the PetriNetService.importPetriNet method, existingNet.getVersion() cannot be null because all existing nets in the system were deployed through processes that ensure every net always has a version assigned.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
📚 Learning: 2025-12-19T07:55:13.427Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 400
File: application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java:239-247
Timestamp: 2025-12-19T07:55:13.427Z
Learning: In the netgrif/application-engine project, the `taskService.findOne()` method (from ITaskService) never returns null. When a task is not found, it handles the error through other means (such as throwing an exception) rather than returning null, so null checks after `findOne()` calls are unnecessary.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
📚 Learning: 2025-12-19T09:06:38.316Z
Learnt from: Retoocs
Repo: netgrif/application-engine PR: 398
File: application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java:57-58
Timestamp: 2025-12-19T09:06:38.316Z
Learning: In the file `application-engine/src/main/java/com/netgrif/application/engine/importer/service/Importer.java`, the `definition` attribute of the `Action` class (from `com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action`) is never null, so null checks for `action.getDefinition()` are not needed.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (8)
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java (5)
32-49: LGTM!Constructor injection with lazy setter injection for
IPetriNetServicecorrectly handles the circular dependency pattern.
51-69: LGTM!The caching logic correctly handles the global functions scope, including the null check and eviction when no functions are present.
114-134: LGTM!Good resilience pattern with per-PetriNet error handling during bulk caching. The pagination logic correctly handles iteration and termination.
136-171: LGTM!The function evaluation and signature validation logic correctly detects duplicate method signatures.
193-199: LGTM!The
getRequiredCachehelper method provides good defensive programming by failing fast with a clear error message when a cache is not configured.application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java (3)
74-75: LGTM!The method correctly includes all new cache names (
petriNetDefault,petriNetLatest,globalFunctions) in the returned set, maintaining insertion order viaLinkedHashSet.
60-65: LGTM. The implementation is correct and well-documented. ThefunctionCachingPageSizeproperty is actively used inFieldActionsCacheService.cacheAllPetriNetFunctions()to paginate through PetriNets when caching their functions, and the default value of 500 is consistent with the legacy configuration.
30-37: Update the legacyapplication-old-dev.propertiesfile to reflect the new cache property names.The migration from
petriNetNewesttopetriNetDefaultandpetriNetLatestis complete in the active codebase. However, the legacy configuration fileapplication-old-dev.properties(lines 38–40) still references the outdatednae.cache.petriNetNewestproperty. While this file is not actively used, update it to prevent developer confusion:
- Replace
nae.cache.petriNetNewest=petriNetNewestwith the new properties:nae.cache.petriNetDefault=petriNetDefaultandnae.cache.petriNetLatest=petriNetLatest- Alternatively, add a comment indicating the file is deprecated/for reference only.
⛔ Skipped due to learnings
Learnt from: renczesstefan Repo: netgrif/application-engine PR: 363 File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69 Timestamp: 2026-01-08T08:37:58.317Z Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.Learnt from: Retoocs Repo: netgrif/application-engine PR: 383 File: application-engine/src/main/java/com/netgrif/application/engine/startup/ApplicationRunnerOrderResolver.java:43-43 Timestamp: 2025-11-14T10:22:01.634Z Learning: For the netgrif/application-engine repository, avoid flagging trivial or nitpick-level issues such as redundant null checks, minor code style improvements, or obvious simplifications that don't affect functionality or introduce bugs. Focus review comments on substantive issues like logic errors, security concerns, performance problems, or breaking changes.
| /** | ||
| * Default cache name for caching global functions of PetriNet global scoped functions. | ||
| */ | ||
| private String globalFunctions = "globalFunctions"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Clarify the documentation wording.
The phrase "caching global functions of PetriNet global scoped functions" is redundant. Consider simplifying to:
- "Default cache name for caching PetriNet global-scoped functions." or
- "Default cache name for caching global functions for PetriNets."
📝 Suggested documentation improvement
/**
- * Default cache name for caching global functions of PetriNet global scoped functions.
+ * Default cache name for caching PetriNet global-scoped functions.
*/
private String globalFunctions = "globalFunctions";Based on learnings, the property name globalFunctions is correct.
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java
around lines 49 - 52, Update the Javadoc for the CacheConfigurationProperties
field globalFunctions to remove the redundant wording; replace "Default cache
name for caching global functions of PetriNet global scoped functions." with a
simpler sentence such as "Default cache name for caching PetriNet global-scoped
functions." (keep field name globalFunctions unchanged).
| * The size of the cache used for managing global Petri net functions. | ||
| */ | ||
| private int namespaceCacheSize = 500; | ||
| private int globalFunctionsCacheSize = 500; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for references to the old namespaceCacheSize property name
echo "=== Searching for Java references to namespaceCacheSize ==="
rg -n --type=java 'namespaceCacheSize|getNamespaceCacheSize|setNamespaceCacheSize'
echo -e "\n=== Searching for property file references ==="
rg -n --type=yaml --type=properties 'namespaceCacheSize|namespace-cache-size|namespace_cache_size'
echo -e "\n=== Searching for test file references ==="
rg -n --type=java 'namespaceCacheSize' -- '**/*.java'
echo -e "\n=== Checking for migration guides or deprecation notes ==="
rg -n -i 'migration|deprecat|namespace.*cache|namespaceCacheSize'Repository: netgrif/application-engine
Length of output: 50486
🏁 Script executed:
#!/bin/bash
# Verify the current state of RunnerConfigurationProperties.java around lines 57-59
echo "=== Reading RunnerConfigurationProperties.java lines 50-65 ==="
sed -n '50,65p' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
echo -e "\n=== Checking for getter/setter methods for globalFunctionsCacheSize ==="
rg -n -A5 -B5 'globalFunctionsCacheSize' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.javaRepository: netgrif/application-engine
Length of output: 932
🏁 Script executed:
#!/bin/bash
# Search for all property definitions in CacheConfigurationProperties and FieldRunnerProperties
echo "=== Checking CacheConfigurationProperties for related properties ==="
find . -name 'CacheConfigurationProperties.java' -exec cat -n {} \;
echo -e "\n=== Searching for uses of globalFunctionsCacheSize in codebase ==="
rg -n 'globalFunctionsCacheSize' --type=javaRepository: netgrif/application-engine
Length of output: 3469
🏁 Script executed:
#!/bin/bash
# Search for usage of cache size properties in implementation code
echo "=== Searching for functionsCacheSize usage ==="
rg -n 'functionsCacheSize' --type=java | head -20
echo -e "\n=== Searching for globalFunctionsCacheSize usage outside property definition ==="
rg -n 'globalFunctionsCacheSize' --type=java
echo -e "\n=== Searching for references to FieldRunnerProperties or cache configuration ==="
rg -n 'FieldRunnerProperties|getGlobalFunctionsCacheSize|getFunctionsCacheSize' --type=java | head -30
echo -e "\n=== Checking if property is actually wired/injected ==="
rg -n 'RunnerConfigurationProperties|FieldRunnerProperties' --type=java -B2 -A2 | head -50Repository: netgrif/application-engine
Length of output: 8490
🏁 Script executed:
#!/bin/bash
# Check where the cache sizes are actually used
echo "=== Searching for cache configuration usage in runner/function classes ==="
fd -t f -name '*.java' -path '*runner*' -o -path '*function*' | xargs rg -l 'cache|Cache' 2>/dev/null | head -10
echo -e "\n=== Check FunctionValidator for cache usage ==="
rg -n 'FunctionValidator' --type=java -A5 -B5 | head -40
echo -e "\n=== Search for any property getter usage pattern ==="
rg -n '\.get[A-Z]' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.javaRepository: netgrif/application-engine
Length of output: 5343
🏁 Script executed:
#!/bin/bash
# Verify the cache configuration wiring to understand the integration gap
echo "=== Reading CacheConfiguration.java around line 52 ==="
sed -n '20,65p' application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
echo -e "\n=== Search for any cache setup for global functions ==="
rg -n 'globalFunctions|GlobalFunctions' --type=java -B3 -A3
echo -e "\n=== Check if there are TODOs or incomplete implementation markers ==="
rg -n 'TODO|FIXME|XXX' application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.javaRepository: netgrif/application-engine
Length of output: 11223
Wire the globalFunctionsCacheSize property into cache configuration.
The globalFunctionsCacheSize property is defined in FieldRunnerProperties but not used in CacheConfiguration. The cache named "globalFunctions" is currently created as a standard ConcurrentMapCache without a size limit, unlike the "functions" cache which respects functionsCacheSize.
Update CacheConfiguration.cacheManager() to create the global functions cache using GenericMapCache with a supplier that applies fieldRunnerProperties.getGlobalFunctionsCacheSize(), similar to how the functions cache is configured (line 52).
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java
around lines 57 - 59, CacheConfiguration.cacheManager() currently registers the
"globalFunctions" cache as a plain ConcurrentMapCache and ignores the
FieldRunnerProperties.globalFunctionsCacheSize; change the registration to
construct the "globalFunctions" cache using GenericMapCache with a supplier that
reads fieldRunnerProperties.getGlobalFunctionsCacheSize(), mirroring how the
"functions" cache is created, so the globalFunctions cache is bounded by the
configured size (update the cacheManager() method to replace the
ConcurrentMapCache instantiation for "globalFunctions" with a GenericMapCache
supplier using fieldRunnerProperties.getGlobalFunctionsCacheSize()).
| @Override | ||
| public void reloadCachedFunctions(String petriNetId) { | ||
| getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId); | ||
| cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache eviction without reload if default version is missing.
If getDefaultVersionByIdentifier returns null (no default version for the identifier), the cache entry is evicted at line 73 but cachePetriNetFunctions silently returns without caching anything. This leaves the functions unavailable with no error indication.
Consider checking the result before evicting, or logging a warning:
Suggested improvement
@Override
public void reloadCachedFunctions(String petriNetId) {
+ PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(petriNetId);
+ if (petriNet == null) {
+ log.warn("Cannot reload cached functions: no default version found for identifier '{}'", petriNetId);
+ return;
+ }
getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId);
- cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId));
+ cachePetriNetFunctions(petriNet);
}🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
around lines 71 - 75, The reloadCachedFunctions method evicts the cache
unconditionally and then calls cachePetriNetFunctions with
petriNetService.getDefaultVersionByIdentifier(petriNetId) which may return null,
leaving the cache empty without notice; update reloadCachedFunctions to first
fetch the default version into a local variable via
petriNetService.getDefaultVersionByIdentifier(petriNetId), check for null, and
if null either skip eviction and log a warning (using the existing logger) or
evict and then log a warning that no default version exists, otherwise proceed
to call cachePetriNetFunctions with the non-null result; reference methods:
reloadCachedFunctions, getRequiredCache(properties.getGlobalFunctions()),
petriNetService.getDefaultVersionByIdentifier(...) and cachePetriNetFunctions.
| @Override | ||
| public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) { | ||
| String stringId = action.getId().toString(); | ||
| if (shouldRewriteCachedActions || !actionsCache.containsKey(stringId)) { | ||
| Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); | ||
| actionsCache.put(stringId, code); | ||
| Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS); | ||
| Object nativeActionsCache = actionsCache.getNativeCache(); | ||
|
|
||
| if (nativeActionsCache instanceof Map<?, ?> map) { | ||
| if (shouldRewriteCachedActions || !map.containsKey(stringId) ) { | ||
| Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); | ||
| actionsCache.put(stringId, code); | ||
| } | ||
| } | ||
| return actionsCache.get(stringId); | ||
| return (Closure) actionsCache.get(stringId).get(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential NullPointerException when cache entry is missing.
actionsCache.get(stringId) returns a Cache.ValueWrapper which is null if the entry doesn't exist. Line 94 unconditionally calls .get() on it, causing an NPE if:
- The native cache is not a
Map(the put at line 91 is skipped entirely) - The entry was evicted between put and get (race condition)
Proposed fix
@Override
public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) {
String stringId = action.getId().toString();
Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS);
- Object nativeActionsCache = actionsCache.getNativeCache();
-
- if (nativeActionsCache instanceof Map<?, ?> map) {
- if (shouldRewriteCachedActions || !map.containsKey(stringId) ) {
- Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
- actionsCache.put(stringId, code);
- }
+
+ Cache.ValueWrapper wrapper = actionsCache.get(stringId);
+ if (shouldRewriteCachedActions || wrapper == null) {
+ Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}");
+ actionsCache.put(stringId, code);
+ return code;
}
- return (Closure) actionsCache.get(stringId).get();
+ return (Closure) wrapper.get();
}🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
around lines 82 - 95, In getCompiledAction modify logic to avoid calling .get()
on a possibly null Cache.ValueWrapper: after obtaining actionsCache and
nativeActionsCache, compute/put the compiled Closure (using shell.evaluate("{->
" + action.getDefinition() + "}")) when needed as you already do, then fetch
Cache.ValueWrapper wrapper = actionsCache.get(stringId) and check for null
before calling wrapper.get(); if wrapper is null (native cache not a Map or
evicted race), return the freshly compiled Closure (or re-evaluate and put it
into actionsCache) so the method always returns a Closure without risking a
NullPointerException. Ensure you reference getCompiledAction, actionsCache,
Cache.ValueWrapper and shell.evaluate in your changes.
| @Override | ||
| public List<CachedFunction> getCachedFunctions(List<com.netgrif.application.engine.objects.petrinet.domain.Function> functions) { | ||
| public List<CachedFunction> getCachedFunctions(List<Function> functions) { | ||
| List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size()); | ||
| functions.forEach(function -> { | ||
| if (!functionsCache.containsKey(function.getStringId())) { | ||
| functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); | ||
| } | ||
| cachedFunctions.add(functionsCache.get(function.getStringId())); | ||
| }); | ||
| Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); | ||
| Object nativeFunctionsCache = functionsCache.getNativeCache(); | ||
|
|
||
| if (nativeFunctionsCache instanceof Map<?, ?> map) { | ||
| functions.forEach(function -> { | ||
| if (!map.containsKey(function.getStringId())) { | ||
| functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); | ||
| } | ||
| cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); | ||
| }); | ||
| } | ||
| return cachedFunctions; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same NullPointerException risk as getCompiledAction.
Line 108 calls .get() on the result of functionsCache.get(), which returns null if the entry doesn't exist. If the native cache is not a Map, the put at line 106 is skipped but line 108 still attempts retrieval.
Proposed fix
@Override
public List<CachedFunction> getCachedFunctions(List<Function> functions) {
List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size());
Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS);
- Object nativeFunctionsCache = functionsCache.getNativeCache();
-
- if (nativeFunctionsCache instanceof Map<?, ?> map) {
- functions.forEach(function -> {
- if (!map.containsKey(function.getStringId())) {
- functionsCache.put(function.getStringId(), CachedFunction.build(shell, function));
- }
- cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get());
- });
- }
+
+ for (Function function : functions) {
+ Cache.ValueWrapper wrapper = functionsCache.get(function.getStringId());
+ CachedFunction cached;
+ if (wrapper == null) {
+ cached = CachedFunction.build(shell, function);
+ functionsCache.put(function.getStringId(), cached);
+ } else {
+ cached = (CachedFunction) wrapper.get();
+ }
+ cachedFunctions.add(cached);
+ }
return cachedFunctions;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public List<CachedFunction> getCachedFunctions(List<com.netgrif.application.engine.objects.petrinet.domain.Function> functions) { | |
| public List<CachedFunction> getCachedFunctions(List<Function> functions) { | |
| List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size()); | |
| functions.forEach(function -> { | |
| if (!functionsCache.containsKey(function.getStringId())) { | |
| functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); | |
| } | |
| cachedFunctions.add(functionsCache.get(function.getStringId())); | |
| }); | |
| Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); | |
| Object nativeFunctionsCache = functionsCache.getNativeCache(); | |
| if (nativeFunctionsCache instanceof Map<?, ?> map) { | |
| functions.forEach(function -> { | |
| if (!map.containsKey(function.getStringId())) { | |
| functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); | |
| } | |
| cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); | |
| }); | |
| } | |
| return cachedFunctions; | |
| } | |
| @Override | |
| public List<CachedFunction> getCachedFunctions(List<Function> functions) { | |
| List<CachedFunction> cachedFunctions = new ArrayList<>(functions.size()); | |
| Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); | |
| for (Function function : functions) { | |
| Cache.ValueWrapper wrapper = functionsCache.get(function.getStringId()); | |
| CachedFunction cached; | |
| if (wrapper == null) { | |
| cached = CachedFunction.build(shell, function); | |
| functionsCache.put(function.getStringId(), cached); | |
| } else { | |
| cached = (CachedFunction) wrapper.get(); | |
| } | |
| cachedFunctions.add(cached); | |
| } | |
| return cachedFunctions; | |
| } |
🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java
around lines 97 - 112, getCachedFunctions has an NPE risk similar to
getCompiledAction because it calls functionsCache.get(...).get() without
guaranteeing the entry exists; if nativeFunctionsCache is not a Map the put is
skipped but the subsequent get() still occurs. Fix by ensuring you never call
.get() on a null Cache.Value: when iterating functions, first retrieve
Cache.Value v = functionsCache.get(id) and if v == null create the
CachedFunction via CachedFunction.build(shell, function) and insert it (use
functionsCache.putIfAbsent / put), then re-read v = functionsCache.get(id) and
only then cast and add ((CachedFunction) v.get()); alternatively use a
compute-if-absent pattern so the cached value is always present before calling
.get(). Ensure this logic is applied in getCachedFunctions (and mirror the same
null-safe pattern used in getCompiledAction).
| @Override | ||
| public Map<String, List<CachedFunction>> getNamespaceFunctionCache() { | ||
| return new HashMap<>(namespaceFunctionsCache); | ||
| public Map<String, List<CachedFunction>> getGlobalFunctionsCache() { | ||
| return new HashMap<>((Map<String, List<CachedFunction>>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unchecked cast assumes Map-backed cache implementation.
The cast to Map<String, List<CachedFunction>> will throw ClassCastException at runtime if the native cache is not a Map. Consider adding a defensive check:
Suggested improvement
@Override
public Map<String, List<CachedFunction>> getGlobalFunctionsCache() {
- return new HashMap<>((Map<String, List<CachedFunction>>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache());
+ Object nativeCache = getRequiredCache(properties.getGlobalFunctions()).getNativeCache();
+ if (nativeCache instanceof Map<?, ?> map) {
+ @SuppressWarnings("unchecked")
+ Map<String, List<CachedFunction>> typedMap = (Map<String, List<CachedFunction>>) map;
+ return new HashMap<>(typedMap);
+ }
+ return Collections.emptyMap();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Override | |
| public Map<String, List<CachedFunction>> getNamespaceFunctionCache() { | |
| return new HashMap<>(namespaceFunctionsCache); | |
| public Map<String, List<CachedFunction>> getGlobalFunctionsCache() { | |
| return new HashMap<>((Map<String, List<CachedFunction>>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache()); | |
| } | |
| @Override | |
| public Map<String, List<CachedFunction>> getGlobalFunctionsCache() { | |
| Object nativeCache = getRequiredCache(properties.getGlobalFunctions()).getNativeCache(); | |
| if (nativeCache instanceof Map<?, ?> map) { | |
| @SuppressWarnings("unchecked") | |
| Map<String, List<CachedFunction>> typedMap = (Map<String, List<CachedFunction>>) map; | |
| return new HashMap<>(typedMap); | |
| } | |
| return Collections.emptyMap(); | |
| } |
…fAbsent and remove mixed synchronization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java:
- Around line 18-22: The constructor GenericMapCache(String name, Class<V>
valueType, int cacheSize) only uses cacheSize as the initial capacity for the
ConcurrentHashMap backing field map, so the cache is unbounded; change the
backing implementation to enforce a maximum size (e.g., replace map with a
Caffeine Cache built with maximumSize(cacheSize) or with a synchronized
LinkedHashMap/ConcurrentLinkedHashMap using access-order eviction and
removeEldestEntry to cap size) and update usages of map in the class to interact
with the chosen cache API (put/get/remove) instead of assuming a raw
ConcurrentHashMap.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.javaapplication-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-08T08:37:58.317Z
Learnt from: renczesstefan
Repo: netgrif/application-engine PR: 363
File: application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java:66-69
Timestamp: 2026-01-08T08:37:58.317Z
Learning: In the CacheConfigurationProperties class, the cache property for namespace/global functions is named `globalFunctions`, not `namespaceFunctions`. This property was renamed as part of the refactoring from namespace-scoped to global-scoped function caching.
Applied to files:
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java
🧬 Code graph analysis (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (1)
application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java (1)
CacheMapKeys(3-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
- GitHub Check: task-list-completed
🔇 Additional comments (4)
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java (1)
49-89: LGTM!The remaining
Cacheinterface methods are implemented correctly:
get(Object key)properly returnsnullwhen key is absent per theCachecontract.- Type-safe retrieval with
type.cast()will throwClassCastExceptionon mismatch as expected.safeCastcorrectly validates types before storing.application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java (3)
30-53: LGTM with a minor note.The cache configuration structure is clean. Using
SimpleCacheManagerwith a mix ofConcurrentMapCache(for general caches) andGenericMapCache(for typed caches) is a reasonable approach.Note:
SimpleCacheManagerimplementsInitializingBean, so Spring will automatically callafterPropertiesSet()after bean initialization to finalize the cache setup.
23-28: Good modernization fromCachingConfigurerSupporttoCachingConfigurer.
CachingConfigurerSupportwas deprecated and removed in Spring 6.x. ImplementingCachingConfigurerdirectly is the correct approach for Spring Boot 3.x compatibility.
44-48: No changes required.The method
getFunctionsCacheSize()is correct. TheFieldRunnerPropertiesclass contains bothfunctionsCacheSizeandglobalFunctionsCacheSizeas separate properties, and the call at line 47 uses the appropriate method for thefunctionsCacheSizeproperty.
| public GenericMapCache(String name, Class<V> valueType, int cacheSize) { | ||
| this.name = name; | ||
| this.valueType = valueType; | ||
| this.map = new ConcurrentHashMap<>(cacheSize); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache is unbounded despite cacheSize parameter name.
The cacheSize parameter is only passed to ConcurrentHashMap as initial capacity, not as a maximum bound. The cache can grow indefinitely, potentially causing memory exhaustion under load. This contradicts the AI summary mentioning "bounded generic caches."
Consider using a bounded cache implementation like Caffeine or implementing LRU eviction:
Option 1: Use Caffeine for bounded caching
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
// In constructor:
this.map = Caffeine.newBuilder()
.maximumSize(cacheSize)
.build();Option 2: Use LinkedHashMap with access-order eviction
private final Map<String, V> map;
public GenericMapCache(String name, Class<V> valueType, int cacheSize) {
this.name = name;
this.valueType = valueType;
this.map = Collections.synchronizedMap(new LinkedHashMap<>(cacheSize, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, V> eldest) {
return size() > cacheSize;
}
});
}🤖 Prompt for AI Agents
In
@application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
around lines 18 - 22, The constructor GenericMapCache(String name, Class<V>
valueType, int cacheSize) only uses cacheSize as the initial capacity for the
ConcurrentHashMap backing field map, so the cache is unbounded; change the
backing implementation to enforce a maximum size (e.g., replace map with a
Caffeine Cache built with maximumSize(cacheSize) or with a synchronized
LinkedHashMap/ConcurrentLinkedHashMap using access-order eviction and
removeEldestEntry to cap size) and update usages of map in the class to interact
with the chosen cache API (put/get/remove) instead of assuming a raw
ConcurrentHashMap.
| public <T> T get(Object key, Callable<T> loader) { | ||
| final String stringKey = String.valueOf(key); | ||
| try { | ||
| V value = map.computeIfAbsent(stringKey, cacheValue -> { | ||
| try { | ||
| T computed = loader.call(); | ||
| if (computed == null) return null; | ||
| return safeCast(computed); | ||
| } | ||
| catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| }); | ||
| return (T) value; | ||
| } catch (RuntimeException e) { | ||
| Throwable cause = (e.getCause() != null) ? e.getCause() : e; | ||
| throw new Cache.ValueRetrievalException(stringKey, loader, cause); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null values are not cached, causing repeated loader invocations.
When loader.call() returns null (line 35), computeIfAbsent does not store the mapping. Subsequent calls with the same key will repeatedly invoke the loader, which may be expensive.
If caching null results is intentional behavior, document it. Otherwise, consider using a sentinel value or Optional<V> internally to distinguish "cached null" from "not cached."
Additionally, line 42 has an unchecked cast from V to T. While safeCast ensures the value is of type V, there's no guarantee T is assignable from V. Consider adding @SuppressWarnings("unchecked") with a comment explaining why this is safe in practice.
Description
This PR introduces a GenericMapCache implementation of Spring’s Cache interface, backed by in-memory Maps. The solution replaces ad-hoc local maps with proper cache wrappers that integrate with Spring Boot Actuator’s /caches endpoints.
This makes all cache operations consistent and manageable via Actuator.
Implements NAE-2182
Blocking PRs
This PR depends on NAE-2212/PR #363
How Has Been This Tested?
The functionality was tested using Postman to verify cache operations through the Actuator endpoints.
Checklist:
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Tests
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.